From 2f5b4397e967c6f416c9efcf657ff1f6176a818b Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Sun, 1 Jun 2025 21:29:32 +0800 Subject: [PATCH 01/22] filter Signed-off-by: Frank Chen --- .../metric/expression/MetricExpression.g4 | 7 +- .../ast/MetricExpressionASTBuilder.java | 50 +++-- .../metric/expression/ast/PredicateEnum.java | 44 ++++- .../expression/pipeline/step/FilterStep.java | 99 ++++++++++ .../ast/MetricExpressionASTBuilderTest.java | 174 +++++++++++------- .../step/BinaryExpressionQueryStepTest.java | 3 +- 6 files changed, 276 insertions(+), 101 deletions(-) create mode 100644 server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java diff --git a/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 b/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 index b2686587d4..0e6f4f7840 100644 --- a/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 +++ b/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 @@ -9,12 +9,13 @@ alertExpression // sum by (a,b,c) (metric {}) metricExpression - : aggregatorExpression LEFT_PARENTHESIS metricQNameExpression labelExpression? RIGHT_PARENTHESIS durationExpression? groupByExpression? #metricAggregationExpression - | metricExpression metricPredicateExpression metricExpectedExpression #metricFilterExpression - | metricExpression (MUL|DIV) metricExpression #arithmeticExpression + : metricExpression (MUL|DIV) metricExpression #arithmeticExpression | metricExpression (ADD|SUB) metricExpression #arithmeticExpression | '(' metricExpression ')' #parenthesisMetricExpression | numberLiteralExpression #metricLiteralExpression // This allows to use literal expression in arithmetic expression + | metricQNameExpression labelExpression? durationExpression? #metricSelectExpression + | metricExpression metricPredicateExpression metricExpectedExpression #metricFilterExpression + | aggregatorExpression LEFT_PARENTHESIS metricQNameExpression labelExpression? RIGHT_PARENTHESIS durationExpression? groupByExpression? #metricAggregationExpression ; aggregatorExpression diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java index 9aeec6e3e3..462ed00e24 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java @@ -188,45 +188,37 @@ public IExpression visitMetricAggregationExpression(MetricExpressionParser.Metri @Override public IExpression visitMetricFilterExpression(MetricExpressionParser.MetricFilterExpressionContext ctx) { - IExpression expression = ctx.metricExpression().accept(this); - if (!(expression instanceof MetricExpression metricExpression)) { - throw new InvalidExpressionException("The metric filter expression must be a metric expression"); - } + IExpression lhs = ctx.metricExpression().accept(this); // // Metric Predicate // MetricExpressionParser.MetricPredicateExpressionContext predicateExpression = ctx.metricPredicateExpression(); - if (predicateExpression != null) { - MetricExpressionParser.MetricExpectedExpressionContext expectedExpression = ctx.metricExpectedExpression(); - LiteralExpression expected = (LiteralExpression) expectedExpression.literalExpression().accept(new LiteralExpressionBuilder()); - - HumanReadableDuration offset = null; - MetricExpressionParser.DurationExpressionContext offsetParseContext = expectedExpression.durationExpression(); - if (offsetParseContext != null) { - offset = offsetParseContext.accept(new DurationExpressionBuilder()); - if (!offset.isNegative()) { - throw new InvalidExpressionException("The value in the offset expression '%s' must be negative.", offsetParseContext.getText()); - } + MetricExpressionParser.MetricExpectedExpressionContext expectedExpression = ctx.metricExpectedExpression(); + LiteralExpression expected = (LiteralExpression) expectedExpression.literalExpression().accept(new LiteralExpressionBuilder()); + + HumanReadableDuration offset = null; + MetricExpressionParser.DurationExpressionContext offsetParseContext = expectedExpression.durationExpression(); + if (offsetParseContext != null) { + offset = offsetParseContext.accept(new DurationExpressionBuilder()); + if (!offset.isNegative()) { + throw new InvalidExpressionException("The value in the offset expression '%s' must be negative.", offsetParseContext.getText()); + } - if (offset.isZero()) { - throw new InvalidExpressionException("The value in the offset express '%s' can't be zero.", offsetParseContext.getText()); - } + if (offset.isZero()) { + throw new InvalidExpressionException("The value in the offset express '%s' can't be zero.", offsetParseContext.getText()); + } - if (!(expected instanceof LiteralExpression.ReadablePercentageLiteral)) { - throw new InvalidExpressionException("The absolute value in the offset expression '%s' is not supported. ONLY percentage is supported now.", expectedExpression.literalExpression().getText()); - } + if (!(expected instanceof LiteralExpression.ReadablePercentageLiteral)) { + throw new InvalidExpressionException("The absolute value in the offset expression '%s' is not supported. ONLY percentage is supported now.", expectedExpression.literalExpression().getText()); } + } - PredicateEnum predicate = getPredicate(predicateExpression.getChild(TerminalNode.class, 0).getSymbol().getType(), - expected, - offset); + PredicateEnum predicate = getPredicate(predicateExpression.getChild(TerminalNode.class, 0).getSymbol().getType(), + expected, + offset); - metricExpression.setPredicate(predicate); - metricExpression.setExpected(expected); - metricExpression.setOffset(offset); - } - return metricExpression; + return predicate.createComparisonExpression(lhs, expected); } private static PredicateEnum getPredicate(int predicateToken, diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/PredicateEnum.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/PredicateEnum.java index bf0df14dd2..dedb9685fc 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/PredicateEnum.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/PredicateEnum.java @@ -16,6 +16,10 @@ package org.bithon.server.metric.expression.ast; +import org.bithon.component.commons.expression.ComparisonExpression; +import org.bithon.component.commons.expression.ConditionalExpression; +import org.bithon.component.commons.expression.IExpression; + /** * @author frank.chen021@outlook.com * @date 2024/7/20 17:12 @@ -23,6 +27,11 @@ public enum PredicateEnum { LT { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.LT(left, right); + } + @Override public String toString() { return "<"; @@ -30,6 +39,11 @@ public String toString() { }, LTE { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.LTE(left, right); + } + @Override public String toString() { return "<="; @@ -37,6 +51,11 @@ public String toString() { }, GT { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.GT(left, right); + } + @Override public String toString() { return ">"; @@ -44,6 +63,12 @@ public String toString() { }, GTE { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.GTE(left, right); + } + + @Override public String toString() { return ">="; @@ -51,21 +76,38 @@ public String toString() { }, NE { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.NE(left, right); + } + @Override public String toString() { return "<>"; } }, EQ { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.EQ(left, right); + } + @Override public String toString() { return "="; } }, IS_NULL { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ConditionalExpression.IsNull(left); + } + @Override public String toString() { return "IS NULL"; } - } + }; + + public abstract IExpression createComparisonExpression(IExpression left, IExpression right); } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java new file mode 100644 index 0000000000..8b5e24b543 --- /dev/null +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java @@ -0,0 +1,99 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.metric.expression.pipeline.step; + + +import org.bithon.component.commons.expression.IDataType; +import org.bithon.component.commons.expression.LiteralExpression; +import org.bithon.server.datasource.query.pipeline.Column; +import org.bithon.server.datasource.query.pipeline.IQueryStep; +import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; + +import java.util.concurrent.CompletableFuture; + +/** + * @author frank.chen021@outlook.com + * @date 1/6/25 9:06 pm + */ +public abstract class FilterStep implements IQueryStep { + protected final IQueryStep source; + protected final LiteralExpression expected; + + protected FilterStep(IQueryStep source, LiteralExpression expected) { + this.source = source; + this.expected = expected; + } + + @Override + public boolean isScalar() { + return source.isScalar(); + } + + @Override + public CompletableFuture execute() throws Exception { + return source.execute() + .thenApply(result -> { + String valColumn = result.getValColumns().get(0); + Column column = result.getTable().getColumn(valColumn); + + int selectionIndex = 0; + int[] selection = new int[result.getTable().rowCount()]; + if (column.getDataType() == IDataType.LONG) { + long expectedValue = ((Number) expected.getValue()).longValue(); + + for (int i = 0, size = column.size(); i < size; i++) { + if (filter(column.getLong(i), expectedValue)) { + selection[selectionIndex++] = i; + } + } + } else if (column.getDataType() == IDataType.DOUBLE) { + double expectedValue = ((Number) expected.getValue()).doubleValue(); + + for (int i = 0, size = column.size(); i < size; i++) { + if (filter(column.getDouble(i), expectedValue)) { + selection[selectionIndex++] = i; + } + } + } else { + throw new UnsupportedOperationException("Unsupported data type: " + column.getDataType()); + } + result.getTable().view(selection, selectionIndex); + + return PipelineQueryResult.builder() + .rows(selectionIndex) + .keyColumns(result.getKeyColumns()) + .valColumns(result.getValColumns()) + .table(result.getTable().view(selection, selectionIndex)) + .build(); + }); + } + + protected abstract boolean filter(long actual, long expected); + + protected abstract boolean filter(double actual, double expected); + + public static class LT extends FilterStep { + public LT(IQueryStep source, LiteralExpression expected) { + super(source, expected); + } + + @Override + protected boolean filter(long actual, long expected); + // Implement filtering logic for LT + } +} +} diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java index 84a63fdc81..9599b7c488 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java @@ -17,6 +17,8 @@ package org.bithon.server.metric.expression.ast; import org.bithon.component.commons.expression.ArithmeticExpression; +import org.bithon.component.commons.expression.BinaryExpression; +import org.bithon.component.commons.expression.ComparisonExpression; import org.bithon.component.commons.expression.IExpression; import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.expression.expt.InvalidExpressionException; @@ -40,11 +42,13 @@ public class MetricExpressionASTBuilderTest { @Test public void test_ColonCharacter() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu:usage{appName = 'a'}) > 1"); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu:usage{appName = 'a'}) > 1"); Assertions.assertNotNull(expression); - Assertions.assertEquals("jvm-metrics", expression.getFrom()); - Assertions.assertEquals("avg", expression.getMetric().getAggregator()); - Assertions.assertEquals("cpu:usage", expression.getMetric().getField()); + + MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + Assertions.assertEquals("jvm-metrics", metricExpression.getFrom()); + Assertions.assertEquals("avg", metricExpression.getMetric().getAggregator()); + Assertions.assertEquals("cpu:usage", metricExpression.getMetric().getField()); Assertions.assertEquals("avg(jvm-metrics.cpu:usage{appName = \"a\"}) > 1", expression.serializeToText()); // colon is not allowed in label @@ -53,11 +57,12 @@ public void test_ColonCharacter() { @Test public void test_Expression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName = 'a'}) > 1"); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName = 'a'}) > 1"); + MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertNotNull(expression); - Assertions.assertEquals("jvm-metrics", expression.getFrom()); - Assertions.assertEquals("avg", expression.getMetric().getAggregator()); - Assertions.assertEquals("cpu", expression.getMetric().getField()); + Assertions.assertEquals("jvm-metrics", metricExpression.getFrom()); + Assertions.assertEquals("avg", metricExpression.getMetric().getAggregator()); + Assertions.assertEquals("cpu", metricExpression.getMetric().getField()); Assertions.assertEquals("avg(jvm-metrics.cpu{appName = \"a\"}) > 1", expression.serializeToText()); } @@ -89,11 +94,12 @@ public void test_Percentage() { @Test public void test_WithLabelSelectorExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <> 'a'}) > 1"); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <> 'a'}) > 1"); + MetricExpression metricSelectExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); Assertions.assertNotNull(expression); - Assertions.assertEquals("jvm-metrics", expression.getFrom()); - Assertions.assertEquals("avg", expression.getMetric().getAggregator()); - Assertions.assertEquals("cpu", expression.getMetric().getField()); + Assertions.assertEquals("jvm-metrics", metricSelectExpression.getFrom()); + Assertions.assertEquals("avg", metricSelectExpression.getMetric().getAggregator()); + Assertions.assertEquals("cpu", metricSelectExpression.getMetric().getField()); MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName > 'a'}) > 1"); MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName >= 'a'}) > 1"); @@ -106,15 +112,18 @@ public void test_WithLabelSelectorExpression() { @Test public void test_DurationExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5m] > 1"); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5m] > 1"); Assertions.assertNotNull(expression); - Assertions.assertEquals(5, expression.getWindow().getDuration().toMinutes()); - Assertions.assertEquals(TimeUnit.MINUTES, expression.getWindow().getUnit()); - expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5h] > 1"); + MetricExpression metricSelectExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + Assertions.assertEquals(5, metricSelectExpression.getWindow().getDuration().toMinutes()); + Assertions.assertEquals(TimeUnit.MINUTES, metricSelectExpression.getWindow().getUnit()); + + expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5h] > 1"); + metricSelectExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); Assertions.assertNotNull(expression); - Assertions.assertEquals(5, expression.getWindow().getDuration().toHours()); - Assertions.assertEquals(TimeUnit.HOURS, expression.getWindow().getUnit()); + Assertions.assertEquals(5, metricSelectExpression.getWindow().getDuration().toHours()); + Assertions.assertEquals(TimeUnit.HOURS, metricSelectExpression.getWindow().getUnit()); // the duration must be a positive value Assertions.assertThrows(InvalidExpressionException.class, () -> MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName in ('a', 1)})[0m] > 1")); @@ -124,25 +133,31 @@ public void test_DurationExpression() { @Test public void test_HumanReadableSizeExpression() { // binary format - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5m] > 1MiB"); - Assertions.assertNotNull(expression); - Assertions.assertEquals(5, expression.getWindow().getDuration().toMinutes()); - Assertions.assertEquals(TimeUnit.MINUTES, expression.getWindow().getUnit()); - Assertions.assertEquals(HumanReadableNumber.of("1MiB"), expression.getExpected().getValue()); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5m] > 1MiB"); + MetricExpression metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + LiteralExpression expected = (LiteralExpression) ((ComparisonExpression) expression).getRhs(); + + Assertions.assertEquals(5, metricExpression.getWindow().getDuration().toMinutes()); + Assertions.assertEquals(TimeUnit.MINUTES, metricExpression.getWindow().getUnit()); + Assertions.assertEquals(HumanReadableNumber.of("1MiB"), expected.getValue()); Assertions.assertEquals("avg(jvm-metrics.cpu{appName <= \"a\"})[5m] > 1MiB", expression.serializeToText()); // decimal format - expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5h] > 7K"); - Assertions.assertNotNull(expression); - Assertions.assertEquals(5, expression.getWindow().getDuration().toHours()); - Assertions.assertEquals(HumanReadableNumber.of("7K"), expression.getExpected().getValue()); + expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5h] > 7K"); + metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + expected = (LiteralExpression) ((ComparisonExpression) expression).getRhs(); + + Assertions.assertEquals(5, metricExpression.getWindow().getDuration().toHours()); + Assertions.assertEquals(HumanReadableNumber.of("7K"), expected.getValue()); Assertions.assertEquals("avg(jvm-metrics.cpu{appName <= \"a\"})[5h] > 7K", expression.serializeToText()); // simplified binary format - expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5h] > 100Gi"); - Assertions.assertNotNull(expression); - Assertions.assertEquals(5, expression.getWindow().getDuration().toHours()); - Assertions.assertEquals(HumanReadableNumber.of("100Gi"), expression.getExpected().getValue()); + expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5h] > 100Gi"); + metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + expected = (LiteralExpression) ((ComparisonExpression) expression).getRhs(); + + Assertions.assertEquals(5, metricExpression.getWindow().getDuration().toHours()); + Assertions.assertEquals(HumanReadableNumber.of("100Gi"), expected.getValue()); Assertions.assertEquals("avg(jvm-metrics.cpu{appName <= \"a\"})[5h] > 100Gi", expression.serializeToText()); // Invalid human-readable size @@ -165,8 +180,9 @@ public void test_SimplePredicateExpression() { @Test public void test_ContainsPredicateExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName contains 'a'})[5m] is null"); - Assertions.assertEquals("appName contains 'a'", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName contains 'a'})[5m] is null"); + MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + Assertions.assertEquals("appName contains 'a'", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // contains require string literal Assertions.assertThrows(InvalidExpressionException.class, () -> MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName contains 5})[5m]")); @@ -174,8 +190,9 @@ public void test_ContainsPredicateExpression() { @Test public void test_StartsWithPredicateExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName startsWith 'a'})[5m] is null"); - Assertions.assertEquals("appName startsWith 'a'", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName startsWith 'a'})[5m] is null"); + MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + Assertions.assertEquals("appName startsWith 'a'", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // startsWith require string literal Assertions.assertThrows(InvalidExpressionException.class, () -> MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName startsWith 5})[5m]")); @@ -183,8 +200,9 @@ public void test_StartsWithPredicateExpression() { @Test public void test_EnsWithPredicateExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName endsWith 'a'})[5m] is null"); - Assertions.assertEquals("appName endsWith 'a'", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName endsWith 'a'})[5m] is null"); + MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + Assertions.assertEquals("appName endsWith 'a'", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // startsWith require string literal Assertions.assertThrows(InvalidExpressionException.class, () -> MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName endsWith 5})[5m]")); @@ -192,8 +210,9 @@ public void test_EnsWithPredicateExpression() { @Test public void test_hasTokenPredicateExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName hasToken 'a', instanceName hasToken '192.'})[5m] > 1"); - Assertions.assertEquals("(appName hasToken 'a') AND (instanceName hasToken '192.')", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName hasToken 'a', instanceName hasToken '192.'})[5m] > 1"); + MetricExpression metricSelectExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + Assertions.assertEquals("(appName hasToken 'a') AND (instanceName hasToken '192.')", metricSelectExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // hasToken require string literal Assertions.assertThrows(InvalidExpressionException.class, () -> MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName hasToken 5})[5m] > 1")); @@ -201,8 +220,9 @@ public void test_hasTokenPredicateExpression() { @Test public void test_RegexMatchPredicateExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{instanceName =~ '192.'})[5m] > 1"); - Assertions.assertEquals("instanceName =~ '192.'", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{instanceName =~ '192.'})[5m] > 1"); + MetricExpression metricSelectExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + Assertions.assertEquals("instanceName =~ '192.'", metricSelectExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // hasToken require string literal Assertions.assertThrows(InvalidExpressionException.class, () -> MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{instanceName =~ ab})[5m] > 1")); @@ -210,8 +230,9 @@ public void test_RegexMatchPredicateExpression() { @Test public void test_RegexNotMatchPredicateExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{instanceName !~ '192.'})[5m] > 1"); - Assertions.assertEquals("instanceName !~ '192.'", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{instanceName !~ '192.'})[5m] > 1"); + MetricExpression metricSelectExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + Assertions.assertEquals("instanceName !~ '192.'", metricSelectExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // hasToken require string literal Assertions.assertThrows(InvalidExpressionException.class, () -> MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{instanceName !~ ab})[5m] > 1")); @@ -220,20 +241,24 @@ public void test_RegexNotMatchPredicateExpression() { @Test public void test_NotPredicateExpression() { // not contains - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not contains 'a'})[5m] is null"); - Assertions.assertEquals("NOT (appName contains 'a')", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not contains 'a'})[5m] is null"); + MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + Assertions.assertEquals("NOT (appName contains 'a')", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // not startsWith - expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not startsWith 'a'})[5m] is null"); - Assertions.assertEquals("NOT (appName startsWith 'a')", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not startsWith 'a'})[5m] is null"); + metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + Assertions.assertEquals("NOT (appName startsWith 'a')", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // not endsWith - expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not endsWith 'a'})[5m] is null"); - Assertions.assertEquals("NOT (appName endsWith 'a')", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not endsWith 'a'})[5m] is null"); + metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + Assertions.assertEquals("NOT (appName endsWith 'a')", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // not hasToken - expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not hasToken 'a'})[5m] is null"); - Assertions.assertEquals("NOT (appName hasToken 'a')", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not hasToken 'a'})[5m] is null"); + metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + Assertions.assertEquals("NOT (appName hasToken 'a')", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); } @Test @@ -253,8 +278,9 @@ public void test_IsNullExpression() { @Test public void test_InExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName in ('a')})[5m] is null"); - Assertions.assertEquals("appName in ('a')", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName in ('a')})[5m] is null"); + MetricExpression metricSelectExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + Assertions.assertEquals("appName in ('a')", metricSelectExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName in ('a', 'b')})[5m] is null"); MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName in (1)})[5m] is null"); @@ -265,8 +291,9 @@ public void test_InExpression() { @Test public void test_NotInExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not in ('a')})[5m] is null"); - Assertions.assertEquals("appName not in ('a')", expression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not in ('a')})[5m] is null"); + MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + Assertions.assertEquals("appName not in ('a')", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not in ('a', 'b')})[5m] is null"); @@ -305,21 +332,21 @@ public void test_OffsetExpression() { public void test_ExpressionSerialization() { // No filter { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu)[5m] > 10%[-7m]"); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu)[5m] > 10%[-7m]"); Assertions.assertEquals("avg(jvm-metrics.cpu)[5m] > 10%[-7m]", expression.serializeToText(IdentifierQuotaStrategy.NONE)); } // One filter { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName = 'a'})[5m] > 10%[-5m]"); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName = 'a'})[5m] > 10%[-5m]"); Assertions.assertEquals("avg(jvm-metrics.cpu{appName = \"a\"})[5m] > 10%[-5m]", expression.serializeToText(IdentifierQuotaStrategy.NONE)); } // Two filters { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName contains 'a', instanceName contains '192.'})[5m] > 10%[-5m]"); + IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName contains 'a', instanceName contains '192.'})[5m] > 10%[-5m]"); Assertions.assertEquals("avg(jvm-metrics.cpu{appName contains \"a\", instanceName contains \"192.\"})[5m] > 10%[-5m]", expression.serializeToText(IdentifierQuotaStrategy.NONE)); } @@ -327,7 +354,7 @@ public void test_ExpressionSerialization() { // count aggregator { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("count( jvm-metrics.cpu{appName contains 'a', instanceName contains '192.'})[5m] > 1"); + IExpression expression = MetricExpressionASTBuilder.parse("count( jvm-metrics.cpu{appName contains 'a', instanceName contains '192.'})[5m] > 1"); Assertions.assertEquals("count(jvm-metrics.cpu{appName contains \"a\", instanceName contains \"192.\"})[5m] > 1", expression.serializeToText()); } @@ -341,21 +368,26 @@ public void test_InvalidMetricName() { @Test public void test_MultipleSelector() { - MetricExpression alertExpression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(http-metrics.responseTime{appName='a', instance='localhost', url='http://localhost/test'})[5m] > 1%[-7m]"); - IExpression whereExpression = alertExpression.getLabelSelectorExpression(); + IExpression expression = MetricExpressionASTBuilder.parse("avg(http-metrics.responseTime{appName='a', instance='localhost', url='http://localhost/test'})[5m] > 1%[-7m]"); + MetricExpression metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + IExpression whereExpression = metricExpression.getLabelSelectorExpression(); Assertions.assertEquals("(appName = 'a') AND (instance = 'localhost') AND (url = 'http://localhost/test')", whereExpression.serializeToText(IdentifierQuotaStrategy.NONE)); } @Test public void test_ByExpression() { - MetricExpression expr = (MetricExpression) MetricExpressionASTBuilder.parse("avg (http-metrics.responseTime{appName='a'})[5m] by (instance) > 1"); - Assertions.assertEquals(Collections.singleton("instance"), expr.getGroupBy()); + IExpression expression = MetricExpressionASTBuilder.parse("avg (http-metrics.responseTime{appName='a'})[5m] by (instance) > 1"); + MetricExpression metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + + Assertions.assertEquals(Collections.singleton("instance"), metricExpression.getGroupBy()); - expr = (MetricExpression) MetricExpressionASTBuilder.parse("avg (http-metrics.responseTime{appName='a'})[5m] by (instance, url) > 1"); - Assertions.assertEquals(new HashSet<>(Arrays.asList("instance", "url")), expr.getGroupBy()); + expression = MetricExpressionASTBuilder.parse("avg (http-metrics.responseTime{appName='a'})[5m] by (instance, url) > 1"); + metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + Assertions.assertEquals(new HashSet<>(Arrays.asList("instance", "url")), metricExpression.getGroupBy()); - expr = (MetricExpression) MetricExpressionASTBuilder.parse("avg (http-metrics.responseTime{appName='a'})[5m] by (instance, url, method) > 1"); - Assertions.assertEquals(new HashSet<>(Arrays.asList("instance", "url", "method")), expr.getGroupBy()); + expression = MetricExpressionASTBuilder.parse("avg (http-metrics.responseTime{appName='a'})[5m] by (instance, url, method) > 1"); + metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + Assertions.assertEquals(new HashSet<>(Arrays.asList("instance", "url", "method")), metricExpression.getGroupBy()); } @Test @@ -526,4 +558,12 @@ public void test_ArithmeticPrecedence_2() { IExpression ast = MetricExpressionASTBuilder.parse(expr); Assertions.assertEquals("(4 * 5) + 6", ast.serializeToText()); } + + + @Test + public void test_MetricArithmeticAndFilter_Precedence() { + String expr = "sum(jvm-metrics.cpu{appName = 'a'})[5m] + 6 * 4 > 5"; + IExpression ast = MetricExpressionASTBuilder.parse(expr); + Assertions.assertEquals("(sum(jvm-metrics.cpu{appName = \"a\"})[5m] + (6 * 4)) > 5", ast.serializeToText()); + } } diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStepTest.java index 710fe44b89..3a98a9861b 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStepTest.java @@ -2666,7 +2666,8 @@ public void test_RelativeComparison() throws Exception { .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) > -5%[-1d]"); - PipelineQueryResult response = evaluator.execute().get(); + PipelineQueryResult response = evaluator.execute() + .get(); Assertions.assertEquals(2, response.getRows()); // Only the overlapped series(app2,app3) will be returned From 97c1169e529624dc2931c6abeca3a3c70b850260 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Mon, 2 Jun 2025 10:49:49 +0800 Subject: [PATCH 02/22] Add filter to ast builder --- .../parser/AlertExpressionASTParser.java | 2 +- .../org/bithon/server/datasource/ISchema.java | 2 - .../ast/MetricExpressionASTBuilder.java | 4 +- .../ast/MetricExpressionOptimizer.java | 81 +++++- .../pipeline/QueryPipelineBuilder.java | 39 +++ .../expression/pipeline/step/FilterStep.java | 107 ++++++-- .../step/MetricExpressionQueryStep.java | 3 +- .../ast/MetricExpressionOptimizerTest.java | 19 +- .../step/BinaryExpressionQueryStepTest.java | 239 ++++++++++++++++++ 9 files changed, 471 insertions(+), 25 deletions(-) diff --git a/server/alerting/common/src/main/java/org/bithon/server/alerting/common/parser/AlertExpressionASTParser.java b/server/alerting/common/src/main/java/org/bithon/server/alerting/common/parser/AlertExpressionASTParser.java index 07a1cad530..a9e0e8762a 100644 --- a/server/alerting/common/src/main/java/org/bithon/server/alerting/common/parser/AlertExpressionASTParser.java +++ b/server/alerting/common/src/main/java/org/bithon/server/alerting/common/parser/AlertExpressionASTParser.java @@ -88,7 +88,7 @@ private static class MetricExpressionBuilder extends MetricExpressionBaseVisitor @Override public IExpression visitAtomicAlertExpression(MetricExpressionParser.AtomicAlertExpressionContext ctx) { - IExpression expression = MetricExpressionASTBuilder.build(ctx.metricExpression()); + IExpression expression = MetricExpressionASTBuilder.toAST(ctx.metricExpression()); if (!(expression instanceof MetricExpression metricExpression)) { throw new InvalidExpressionException("Complex expression is not supported now."); } diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/ISchema.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/ISchema.java index 316ee8d70e..d6c0f3938d 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/ISchema.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/ISchema.java @@ -64,6 +64,4 @@ default boolean isVirtual() { } Period getTtl(); - - } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java index 462ed00e24..defbb85cf7 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java @@ -87,10 +87,10 @@ public static IExpression parse(String expression) { "Unexpected token"); } - return build(ctx); + return toAST(ctx); } - public static IExpression build(MetricExpressionParser.MetricExpressionContext metricExpression) { + public static IExpression toAST(MetricExpressionParser.MetricExpressionContext metricExpression) { return metricExpression.accept(new BuilderImpl()); } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java index 19c7155938..1ad588b6b6 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java @@ -17,7 +17,12 @@ package org.bithon.server.metric.expression.ast; +import org.bithon.component.commons.expression.ArithmeticExpression; +import org.bithon.component.commons.expression.ComparisonExpression; +import org.bithon.component.commons.expression.ConditionalExpression; import org.bithon.component.commons.expression.IExpression; +import org.bithon.component.commons.expression.LiteralExpression; +import org.bithon.component.commons.expression.optimzer.AbstractOptimizer; import org.bithon.component.commons.expression.optimzer.ConstantFoldingOptimizer; /** @@ -37,8 +42,82 @@ public IExpression visit(MetricExpression expression) { } public static IExpression optimize(IExpression expression) { - return expression.accept(new ExtendedConstantFoldingOptimizer()); + return expression.accept(new ExtendedConstantFoldingOptimizer()) + //.accept(new OperatorPushingOptimizer()) + ; } + /** + * for comparison expression: sum(metric) > 5, + * it's parsed as: + *
+     *     ComparisonExpression
+     *     ├── MetricExpression
+     *     └── ConstantExpression
+     * 
+ * We can push down the predicate expression and constant expression to the metric expression as + *
+     *     MetricExpression
+     *     └── ExpectedExpression(ConstantExpression)
+     * 
+ *

+ * for arithmetic expression: sum(metric) + 5, + * it's parsed as: + *

+     *     ArithmeticExpression
+     *     ├── MetricExpression
+     *     └── ConstantExpression
+     *     
+ * We can push down the constant expression to the metric expression as + *
+     *     MetricExpression
+     *     └── PostExpression(ConstantExpression)
+     *  
+ */ + private static class OperatorPushingOptimizer extends AbstractOptimizer implements IMetricExpressionVisitor { + @Override + public IExpression visit(MetricExpression expression) { + return expression; + } + + @Override + public IExpression visit(ConditionalExpression expression) { + IExpression lhs = expression.getLhs().accept(this); + IExpression rhs = expression.getRhs().accept(this); + + if (lhs instanceof MetricExpression metricExpression + && rhs instanceof LiteralExpression expectedExpression) { + metricExpression.setExpected(expectedExpression); + if (expression instanceof ComparisonExpression.LT) { + metricExpression.setPredicate(PredicateEnum.LT); + } else if (expression instanceof ComparisonExpression.GT) { + metricExpression.setPredicate(PredicateEnum.GT); + } else if (expression instanceof ComparisonExpression.LTE) { + metricExpression.setPredicate(PredicateEnum.LTE); + } else if (expression instanceof ComparisonExpression.GTE) { + metricExpression.setPredicate(PredicateEnum.GTE); + } else if (expression instanceof ComparisonExpression.EQ) { + metricExpression.setPredicate(PredicateEnum.EQ); + } else if (expression instanceof ComparisonExpression.NE) { + metricExpression.setPredicate(PredicateEnum.NE); + } else { + throw new UnsupportedOperationException("Unsupported comparison expression: " + expression.getClass().getSimpleName()); + } + return metricExpression; + } + return expression; + } + + /** + * for expression like: sum(metric) + sum(metric2), apply optimization if: + * 1. they're on the same data source with same filters and same group-by, + * 2. the underlying data source supports multiple metrics in a single query + * then we can merge them into a single metric expression + */ + @Override + public IExpression visit(ArithmeticExpression expression) { + return super.visit(expression); + } + } } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java index e27b90a1ff..861873c080 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java @@ -18,6 +18,8 @@ import org.bithon.component.commons.expression.ArithmeticExpression; +import org.bithon.component.commons.expression.ComparisonExpression; +import org.bithon.component.commons.expression.ConditionalExpression; import org.bithon.component.commons.expression.IExpression; import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.utils.StringUtils; @@ -28,6 +30,7 @@ import org.bithon.server.metric.expression.ast.MetricExpressionASTBuilder; import org.bithon.server.metric.expression.ast.MetricExpressionOptimizer; import org.bithon.server.metric.expression.pipeline.step.BinaryExpressionQueryStep; +import org.bithon.server.metric.expression.pipeline.step.FilterStep; import org.bithon.server.metric.expression.pipeline.step.LiteralQueryStep; import org.bithon.server.metric.expression.pipeline.step.MetricExpressionQueryStep; import org.bithon.server.web.service.datasource.api.IDataSourceApi; @@ -164,5 +167,41 @@ public IQueryStep visit(ArithmeticExpression expression) { default -> throw new UnsupportedOperationException("Unsupported arithmetic expression: " + expression.getType()); }; } + + @Override + public IQueryStep visit(ConditionalExpression expression) { + IQueryStep source = expression.getLhs().accept(this); + if (expression instanceof ComparisonExpression.LT) { + return new FilterStep.LT( + source, + (LiteralExpression) expression.getRhs() + ); + } + if (expression instanceof ComparisonExpression.LTE) { + return new FilterStep.LTE( + source, + (LiteralExpression) expression.getRhs() + ); + } + if (expression instanceof ComparisonExpression.GT) { + return new FilterStep.GT( + source, + (LiteralExpression) expression.getRhs() + ); + } + if (expression instanceof ComparisonExpression.GTE) { + return new FilterStep.GTE( + source, + (LiteralExpression) expression.getRhs() + ); + } + if (expression instanceof ComparisonExpression.NE) { + return new FilterStep.NE( + source, + (LiteralExpression) expression.getRhs() + ); + } + throw new UnsupportedOperationException("Unsupported conditional expression: " + expression.getType()); + } } } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java index 8b5e24b543..91e5750c03 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java @@ -20,6 +20,7 @@ import org.bithon.component.commons.expression.IDataType; import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.server.datasource.query.pipeline.Column; +import org.bithon.server.datasource.query.pipeline.ColumnarTable; import org.bithon.server.datasource.query.pipeline.IQueryStep; import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; @@ -50,14 +51,14 @@ public CompletableFuture execute() throws Exception { String valColumn = result.getValColumns().get(0); Column column = result.getTable().getColumn(valColumn); - int selectionIndex = 0; - int[] selection = new int[result.getTable().rowCount()]; + int filteredRowCount = 0; + int[] filteredRows = new int[result.getTable().rowCount()]; if (column.getDataType() == IDataType.LONG) { long expectedValue = ((Number) expected.getValue()).longValue(); for (int i = 0, size = column.size(); i < size; i++) { if (filter(column.getLong(i), expectedValue)) { - selection[selectionIndex++] = i; + filteredRows[filteredRowCount++] = i; } } } else if (column.getDataType() == IDataType.DOUBLE) { @@ -65,20 +66,27 @@ public CompletableFuture execute() throws Exception { for (int i = 0, size = column.size(); i < size; i++) { if (filter(column.getDouble(i), expectedValue)) { - selection[selectionIndex++] = i; + filteredRows[filteredRowCount++] = i; } } } else { throw new UnsupportedOperationException("Unsupported data type: " + column.getDataType()); } - result.getTable().view(selection, selectionIndex); - - return PipelineQueryResult.builder() - .rows(selectionIndex) - .keyColumns(result.getKeyColumns()) - .valColumns(result.getValColumns()) - .table(result.getTable().view(selection, selectionIndex)) - .build(); + + if (filteredRowCount < filteredRows.length) { + ColumnarTable newTable = result.getTable() + .view(filteredRows, filteredRowCount); + + return PipelineQueryResult.builder() + .rows(filteredRowCount) + .table(newTable) + .keyColumns(result.getKeyColumns()) + .valColumns(result.getValColumns()) + .build(); + } else { + // All results are filtered out, no need to apply a filter view on top of it + return result; + } }); } @@ -92,8 +100,77 @@ public LT(IQueryStep source, LiteralExpression expected) { } @Override - protected boolean filter(long actual, long expected); - // Implement filtering logic for LT + protected boolean filter(long actual, long expected) { + return actual < expected; + } + + @Override + protected boolean filter(double actual, double expected) { + return actual < expected; + } + } + + public static class LTE extends FilterStep { + public LTE(IQueryStep source, LiteralExpression expected) { + super(source, expected); + } + + @Override + protected boolean filter(long actual, long expected) { + return actual <= expected; + } + + @Override + protected boolean filter(double actual, double expected) { + return actual <= expected; + } + } + + public static class GT extends FilterStep { + public GT(IQueryStep source, LiteralExpression expected) { + super(source, expected); + } + + @Override + protected boolean filter(long actual, long expected) { + return actual > expected; + } + + @Override + protected boolean filter(double actual, double expected) { + return actual > expected; + } + } + + public static class GTE extends FilterStep { + public GTE(IQueryStep source, LiteralExpression expected) { + super(source, expected); + } + + @Override + protected boolean filter(long actual, long expected) { + return actual >= expected; + } + + @Override + protected boolean filter(double actual, double expected) { + return actual >= expected; + } + } + + public static class NE extends FilterStep { + public NE(IQueryStep source, LiteralExpression expected) { + super(source, expected); + } + + @Override + protected boolean filter(long actual, long expected) { + return actual != expected; + } + + @Override + protected boolean filter(double actual, double expected) { + return actual != expected; + } } -} } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricExpressionQueryStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricExpressionQueryStep.java index 3184cdbe93..4eae3bdf63 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricExpressionQueryStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricExpressionQueryStep.java @@ -45,7 +45,8 @@ public class MetricExpressionQueryStep implements IQueryStep { // Make sure the evaluation is executed ONLY ONCE when the expression is referenced multiple times private volatile CompletableFuture cachedResponse; - public MetricExpressionQueryStep(QueryRequest queryRequest, IDataSourceApi dataSourceApi) { + public MetricExpressionQueryStep(QueryRequest queryRequest, + IDataSourceApi dataSourceApi) { this.queryRequest = queryRequest; this.dataSourceApi = dataSourceApi; this.isScalar = computeIsScalar(queryRequest); diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java index b8fb85bd07..3bb9e5db65 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java @@ -17,6 +17,7 @@ package org.bithon.server.metric.expression.ast; +import org.bithon.component.commons.expression.ComparisonExpression; import org.bithon.component.commons.expression.IExpression; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -50,12 +51,12 @@ public void test_Optimize_ConstantFolding_Mul() { @Test public void test_Optimize_ConstantFolding_2() { - String expression = "1 + sum(dataSource.metric) + 2 + 3 + 4"; + String expression = "1 + sum(dataSource.metric) + 2 * 3 + 4"; IExpression ast = MetricExpressionASTBuilder.parse(expression); - Assertions.assertEquals("(((1 + sum(dataSource.metric)) + 2) + 3) + 4", ast.serializeToText()); + Assertions.assertEquals("((1 + sum(dataSource.metric)) + (2 * 3)) + 4", ast.serializeToText()); ast = MetricExpressionOptimizer.optimize(ast); - Assertions.assertEquals("sum(dataSource.metric) + 10", ast.serializeToText()); + Assertions.assertEquals("sum(dataSource.metric) + 11", ast.serializeToText()); } @Test @@ -93,4 +94,16 @@ public void test_Optimize_ConstantFolding_Duration() { ast = MetricExpressionOptimizer.optimize(ast); Assertions.assertEquals("sum(dataSource.metric) + 86460", ast.serializeToText()); } + + @Test + public void test_Optimize_PushDownFilterExpression() { + String expression = "sum(dataSource.metric) > 5"; + IExpression ast = MetricExpressionASTBuilder.parse(expression); + Assertions.assertInstanceOf(ComparisonExpression.class, ast); + Assertions.assertEquals("sum(dataSource.metric) > 5", ast.serializeToText()); + + ast = MetricExpressionOptimizer.optimize(ast); + Assertions.assertInstanceOf(MetricExpression.class, ast); + Assertions.assertEquals("sum(dataSource.metric) > 5", ast.serializeToText()); + } } diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStepTest.java index 3a98a9861b..607de5e96a 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStepTest.java @@ -2690,4 +2690,243 @@ public void test_RelativeComparison() throws Exception { Assertions.assertEquals((5.0 - 22) / 22, valCol.getDouble(1), .0000000001); } } + + @Test + public void test_FilterStep_Double_GT() throws Exception { + Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + .thenAnswer((answer) -> { + return ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 3, 4, 5) + ); + }); + + // + // Case 1, > 2 + // + { + IQueryStep evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 2"); + PipelineQueryResult response = evaluator.execute().get(); + + // all 3 records satisfy the filter condition + Assertions.assertEquals(3, response.getRows()); + + // Only the overlapped series(app2,app3) will be returned + { + Column valCol = response.getTable().getColumn("activeThreads"); + Assertions.assertEquals(3, valCol.size()); + Assertions.assertEquals(3, valCol.getDouble(0), .0000000001); + Assertions.assertEquals(4, valCol.getDouble(1), .0000000001); + Assertions.assertEquals(5, valCol.getDouble(2), .0000000001); + } + } + + // + // Case 2, > 3, tow rows satisfy the filter condition + // + { + IQueryStep evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 3"); + PipelineQueryResult response = evaluator.execute().get(); + + Assertions.assertEquals(2, response.getRows()); + { + Column valCol = response.getTable().getColumn("activeThreads"); + Assertions.assertEquals(2, valCol.size()); + Assertions.assertEquals(4, valCol.getDouble(0), .0000000001); + Assertions.assertEquals(5, valCol.getDouble(1), .0000000001); + } + } + + // + // Case 3, > 4, 1 rows satisfies the filter condition + // + { + IQueryStep evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 4"); + PipelineQueryResult response = evaluator.execute().get(); + + Assertions.assertEquals(1, response.getRows()); + { + Column valCol = response.getTable().getColumn("activeThreads"); + Assertions.assertEquals(1, valCol.size()); + Assertions.assertEquals(5, valCol.getDouble(0), .0000000001); + } + } + + + // + // Case 4, > 5, 0 rows satisfies the filter condition + // + { + IQueryStep evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 5"); + PipelineQueryResult response = evaluator.execute().get(); + + Assertions.assertEquals(0, response.getRows()); + { + Column valCol = response.getTable().getColumn("activeThreads"); + Assertions.assertEquals(0, valCol.size()); + } + } + } + + @Test + public void test_FilterStep_Double_GTE() throws Exception { + Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + .thenAnswer((answer) -> { + return ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 3, 4, 5) + ); + }); + + // + // Case 1, >= 2, all 3 rows satisfy the filter condition + // + { + IQueryStep evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 2"); + PipelineQueryResult response = evaluator.execute().get(); + + // all 3 records satisfy the filter condition + Assertions.assertEquals(3, response.getRows()); + { + Column valCol = response.getTable().getColumn("activeThreads"); + Assertions.assertEquals(3, valCol.size()); + Assertions.assertEquals(3, valCol.getDouble(0), .0000000001); + Assertions.assertEquals(4, valCol.getDouble(1), .0000000001); + Assertions.assertEquals(5, valCol.getDouble(2), .0000000001); + } + } + + // + // Case 2, >= 3, all 3 rows satisfy the filter condition + // + { + IQueryStep evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 3"); + PipelineQueryResult response = evaluator.execute().get(); + + Assertions.assertEquals(3, response.getRows()); + { + Column valCol = response.getTable().getColumn("activeThreads"); + Assertions.assertEquals(3, valCol.size()); + Assertions.assertEquals(3, valCol.getDouble(0), .0000000001); + Assertions.assertEquals(4, valCol.getDouble(1), .0000000001); + Assertions.assertEquals(5, valCol.getDouble(2), .0000000001); + } + } + + // + // Case 3, >= 4, 2 rows satisfies the filter condition + // + { + IQueryStep evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 4"); + PipelineQueryResult response = evaluator.execute().get(); + + Assertions.assertEquals(2, response.getRows()); + { + Column valCol = response.getTable().getColumn("activeThreads"); + Assertions.assertEquals(2, valCol.size()); + Assertions.assertEquals(4, valCol.getDouble(0), .0000000001); + Assertions.assertEquals(5, valCol.getDouble(1), .0000000001); + } + } + + // + // Case 4, >= 5, 1 rows satisfies the filter condition + // + { + IQueryStep evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 5"); + PipelineQueryResult response = evaluator.execute().get(); + + Assertions.assertEquals(1, response.getRows()); + { + Column valCol = response.getTable().getColumn("activeThreads"); + Assertions.assertEquals(1, valCol.size()); + Assertions.assertEquals(5, valCol.getDouble(0), .0000000001); + } + } + + // + // Case 5, >= 6, 0 rows satisfies the filter condition + // + { + IQueryStep evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 6"); + PipelineQueryResult response = evaluator.execute().get(); + + Assertions.assertEquals(0, response.getRows()); + { + Column valCol = response.getTable().getColumn("activeThreads"); + Assertions.assertEquals(0, valCol.size()); + } + } + } } From c8374335d4fe981e912e4b435c7ed702ed375c87 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Mon, 2 Jun 2025 11:57:49 +0800 Subject: [PATCH 03/22] Add metric select expression --- .../ast/IMetricExpressionVisitor.java | 4 + .../ast/MetricExpressionASTBuilder.java | 41 ++++ .../ast/MetricSelectExpression.java | 191 ++++++++++++++++++ .../ast/MetricExpressionASTBuilderTest.java | 17 +- 4 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricSelectExpression.java diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java index 0298b5482c..ece6135fd6 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java @@ -24,5 +24,9 @@ * @date 4/4/25 3:55 pm */ public interface IMetricExpressionVisitor extends IExpressionVisitor { + default T visit(MetricSelectExpression expression) { + return null; + } + T visit(MetricExpression expression); } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java index defbb85cf7..d08b99b4e6 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java @@ -186,6 +186,47 @@ public IExpression visitMetricAggregationExpression(MetricExpressionParser.Metri return expression; } + @Override + public IExpression visitMetricSelectExpression(MetricExpressionParser.MetricSelectExpressionContext ctx) { + String[] names = ctx.metricQNameExpression().getText().split("\\."); + String from = names[0]; + String metric = names[1]; + + HumanReadableDuration duration = null; + MetricExpressionParser.DurationExpressionContext windowExpressionCtx = ctx.durationExpression(); + if (windowExpressionCtx != null) { + duration = windowExpressionCtx.accept(new DurationExpressionBuilder()); + if (duration.isNegative()) { + throw new InvalidExpressionException(StringUtils.format("The integer literal in duration expression '%s' must be greater than zero", windowExpressionCtx.getText())); + } + if (duration.isZero()) { + throw new InvalidExpressionException(StringUtils.format("The integer literal in duration expression '%s' must be greater than zero", windowExpressionCtx.getText())); + } + } + + IExpression whereExpression = null; + MetricExpressionParser.LabelExpressionContext where = ctx.labelExpression(); + if (where != null) { + LabelSelectorExpressionBuilder filterASTBuilder = new LabelSelectorExpressionBuilder(); + List filters = where.labelSelectorExpression() + .stream() + .map((filter) -> filter.accept(filterASTBuilder)) + .collect(Collectors.toList()); + if (filters.size() == 1) { + whereExpression = filters.get(0); + } else if (filters.size() > 1) { + whereExpression = new LogicalExpression.AND(filters); + } + } + + MetricSelectExpression expression = new MetricSelectExpression(); + expression.setFrom(from); + expression.setLabelSelectorExpression(whereExpression); + expression.setMetric(metric); + expression.setWindow(duration); + return expression; + } + @Override public IExpression visitMetricFilterExpression(MetricExpressionParser.MetricFilterExpressionContext ctx) { IExpression lhs = ctx.metricExpression().accept(this); diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricSelectExpression.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricSelectExpression.java new file mode 100644 index 0000000000..e8dc8367d0 --- /dev/null +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricSelectExpression.java @@ -0,0 +1,191 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.metric.expression.ast; + + +import lombok.Getter; +import lombok.Setter; +import org.bithon.component.commons.expression.IDataType; +import org.bithon.component.commons.expression.IEvaluationContext; +import org.bithon.component.commons.expression.IExpression; +import org.bithon.component.commons.expression.IExpressionInDepthVisitor; +import org.bithon.component.commons.expression.IExpressionVisitor; +import org.bithon.component.commons.expression.LiteralExpression; +import org.bithon.component.commons.expression.LogicalExpression; +import org.bithon.component.commons.expression.expt.InvalidExpressionException; +import org.bithon.component.commons.expression.serialization.ExpressionSerializer; +import org.bithon.component.commons.expression.serialization.IdentifierQuotaStrategy; +import org.bithon.component.commons.utils.HumanReadableDuration; +import org.bithon.component.commons.utils.StringUtils; +import org.bithon.server.datasource.ISchema; +import org.bithon.server.datasource.column.IColumn; + +import java.util.Map; + +/** + * @author frank.chen021@outlook.com + * @date 2/6/25 11:46 am + */ +@Getter +public class MetricSelectExpression implements IExpression { + @Setter + private String from; + + @Setter + private String metric; + + @Setter + private HumanReadableDuration window; + + /** + * Runtime properties + */ + private IExpression labelSelectorExpression; + private String labelSelectorText; + private String whereText; + + public void setLabelSelectorExpression(IExpression labelSelectorExpression) { + this.labelSelectorExpression = labelSelectorExpression; + + // The where property holds the internal expression that will be passed to the query module + this.whereText = labelSelectorExpression == null ? null : new MetricExpression.SqlStyleSerializer().serialize(labelSelectorExpression); + + // This variable holds the raw text + this.labelSelectorText = labelSelectorExpression == null ? "" : "{" + new MetricExpression.LabelSelectorExpressionSerializer().serialize(labelSelectorExpression) + "}"; + } + + @Override + public String serializeToText() { + StringBuilder sb = new StringBuilder(); + sb.append(StringUtils.format("%s.%s%s", + from, + // Use field for serialization because it holds the raw input. + // In some cases, like the 'count' aggregator, the name property has different values from the field property + // See AlertExpressionASTParser for more + metric, + this.labelSelectorText)); + + if (window != null) { + sb.append('['); + sb.append(window); + sb.append(']'); + } + + return sb.toString(); + } + + @Override + public void serializeToText(ExpressionSerializer serializer) { + serializer.append(serializeToText()); + } + + static class SqlStyleSerializer extends ExpressionSerializer { + public SqlStyleSerializer() { + super(null); + } + + @Override + public void serialize(LiteralExpression expression) { + if (expression instanceof LiteralExpression.StringLiteral stringLiteral) { + sb.append('\''); + sb.append(StringUtils.escape(stringLiteral.getValue(), '\\', '\'')); + sb.append('\''); + } else { + sb.append(expression.getValue()); + } + } + } + + static class LabelSelectorExpressionSerializer extends ExpressionSerializer { + + public LabelSelectorExpressionSerializer() { + super(IdentifierQuotaStrategy.NONE); + } + + @Override + public void serialize(LogicalExpression expression) { + for (int i = 0, size = expression.getOperands().size(); i < size; i++) { + if (i > 0) { + sb.append(", "); + } + expression.getOperands().get(i).serializeToText(this); + } + } + + // Use double quote to serialize the expression by default + @Override + public void serialize(LiteralExpression expression) { + if (expression instanceof LiteralExpression.StringLiteral stringLiteral) { + sb.append('"'); + sb.append(StringUtils.escape(stringLiteral.getValue(), '\\', '"')); + sb.append('"'); + } else { + sb.append(expression.getValue()); + } + } + } + + @Override + public IDataType getDataType() { + return IDataType.BOOLEAN; + } + + @Override + public String getType() { + // No need + return null; + } + + @Override + public Object evaluate(IEvaluationContext context) { + throw new UnsupportedOperationException("Evaluate an alert expression is not supported."); + } + + @Override + public void accept(IExpressionInDepthVisitor visitor) { + } + + @Override + public T accept(IExpressionVisitor visitor) { + if (visitor instanceof IMetricExpressionVisitor metricExpressionVisitor) { + return metricExpressionVisitor.visit(this); + } + throw new UnsupportedOperationException("Evaluate an alert expression is not supported."); + } + + public void validate(Map schemas) { + if (!StringUtils.hasText(this.getFrom())) { + throw new InvalidExpressionException("data-source expression is missed in expression [%s]", this.serializeToText()); + } + + ISchema schema = schemas.get(this.getFrom()); + if (schema == null) { + throw new InvalidExpressionException("data-source expression [%s] does not exist for expression [%s]", + this.getFrom(), + this.serializeToText()); + } + + String metricName = this.getMetric(); + IColumn metricSpec = schema.getColumnByName(metricName); + if (metricSpec == null) { + throw new InvalidExpressionException("Metric [%s] in expression [%s] does not exist in data-source [%s]", + metricName, + this.serializeToText(), + this.getFrom()); + } + } +} diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java index 9599b7c488..7fe033e6ed 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java @@ -559,11 +559,26 @@ public void test_ArithmeticPrecedence_2() { Assertions.assertEquals("(4 * 5) + 6", ast.serializeToText()); } - @Test public void test_MetricArithmeticAndFilter_Precedence() { String expr = "sum(jvm-metrics.cpu{appName = 'a'})[5m] + 6 * 4 > 5"; IExpression ast = MetricExpressionASTBuilder.parse(expr); Assertions.assertEquals("(sum(jvm-metrics.cpu{appName = \"a\"})[5m] + (6 * 4)) > 5", ast.serializeToText()); } + + @Test + public void test_MetricSelectExpression() { + { + String expr = "jvm-metrics. cpu"; + Assertions.assertEquals("jvm-metrics.cpu", MetricExpressionASTBuilder.parse(expr).serializeToText()); + } + { + String expr = "jvm-metrics.cpu{appName = 'a'}"; + Assertions.assertEquals("jvm-metrics.cpu{appName = \"a\"}", MetricExpressionASTBuilder.parse(expr).serializeToText()); + } + { + String expr = "jvm-metrics.cpu{appName = 'a'}[ 5m ]"; + Assertions.assertEquals("jvm-metrics.cpu{appName = \"a\"}[5m]", MetricExpressionASTBuilder.parse(expr).serializeToText()); + } + } } From 62ec42398ae7e082a4c0ac655532880d749df76b Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Mon, 2 Jun 2025 20:29:27 +0800 Subject: [PATCH 04/22] Rename MetricExpression to MetricAggregateExpression --- .../common/model/AlertExpression.java | 4 +- .../parser/AlertExpressionASTParser.java | 6 +-- .../metric/expression/MetricExpression.g4 | 2 + .../ast/IMetricExpressionVisitor.java | 2 +- ...on.java => MetricAggregateExpression.java} | 2 +- .../ast/MetricExpressionASTBuilder.java | 2 +- .../ast/MetricExpressionOptimizer.java | 15 ++++-- .../ast/MetricSelectExpression.java | 4 +- .../pipeline/QueryPipelineBuilder.java | 4 +- .../ast/MetricExpressionASTBuilderTest.java | 54 +++++++++---------- .../ast/MetricExpressionOptimizerTest.java | 4 +- 11 files changed, 54 insertions(+), 45 deletions(-) rename server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/{MetricExpression.java => MetricAggregateExpression.java} (99%) diff --git a/server/alerting/common/src/main/java/org/bithon/server/alerting/common/model/AlertExpression.java b/server/alerting/common/src/main/java/org/bithon/server/alerting/common/model/AlertExpression.java index a96f8f0437..722a6cfc05 100644 --- a/server/alerting/common/src/main/java/org/bithon/server/alerting/common/model/AlertExpression.java +++ b/server/alerting/common/src/main/java/org/bithon/server/alerting/common/model/AlertExpression.java @@ -24,7 +24,7 @@ import org.bithon.component.commons.expression.IExpressionVisitor; import org.bithon.component.commons.expression.serialization.ExpressionSerializer; import org.bithon.server.alerting.common.evaluator.metric.IMetricEvaluator; -import org.bithon.server.metric.expression.ast.MetricExpression; +import org.bithon.server.metric.expression.ast.MetricAggregateExpression; /** * NOTE: When changes are made to this class, @@ -52,7 +52,7 @@ public class AlertExpression implements IExpression { private String id; - private MetricExpression metricExpression; + private MetricAggregateExpression metricExpression; private IMetricEvaluator metricEvaluator; @Override diff --git a/server/alerting/common/src/main/java/org/bithon/server/alerting/common/parser/AlertExpressionASTParser.java b/server/alerting/common/src/main/java/org/bithon/server/alerting/common/parser/AlertExpressionASTParser.java index a9e0e8762a..7c8828e86c 100644 --- a/server/alerting/common/src/main/java/org/bithon/server/alerting/common/parser/AlertExpressionASTParser.java +++ b/server/alerting/common/src/main/java/org/bithon/server/alerting/common/parser/AlertExpressionASTParser.java @@ -44,7 +44,7 @@ import org.bithon.server.metric.expression.MetricExpressionBaseVisitor; import org.bithon.server.metric.expression.MetricExpressionLexer; import org.bithon.server.metric.expression.MetricExpressionParser; -import org.bithon.server.metric.expression.ast.MetricExpression; +import org.bithon.server.metric.expression.ast.MetricAggregateExpression; import org.bithon.server.metric.expression.ast.MetricExpressionASTBuilder; import java.util.List; @@ -89,7 +89,7 @@ private static class MetricExpressionBuilder extends MetricExpressionBaseVisitor @Override public IExpression visitAtomicAlertExpression(MetricExpressionParser.AtomicAlertExpressionContext ctx) { IExpression expression = MetricExpressionASTBuilder.toAST(ctx.metricExpression()); - if (!(expression instanceof MetricExpression metricExpression)) { + if (!(expression instanceof MetricAggregateExpression metricExpression)) { throw new InvalidExpressionException("Complex expression is not supported now."); } @@ -148,7 +148,7 @@ public IExpression visitLogicalAlertExpression(MetricExpressionParser.LogicalAle } } - private static IMetricEvaluator createMetricEvaluator(MetricExpression metricExpression) { + private static IMetricEvaluator createMetricEvaluator(MetricAggregateExpression metricExpression) { LiteralExpression expected = metricExpression.getExpected(); HumanReadableDuration offset = metricExpression.getOffset(); diff --git a/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 b/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 index 0e6f4f7840..eda03d950e 100644 --- a/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 +++ b/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 @@ -15,7 +15,9 @@ metricExpression | numberLiteralExpression #metricLiteralExpression // This allows to use literal expression in arithmetic expression | metricQNameExpression labelExpression? durationExpression? #metricSelectExpression | metricExpression metricPredicateExpression metricExpectedExpression #metricFilterExpression + // Legacy metric aggregation expression | aggregatorExpression LEFT_PARENTHESIS metricQNameExpression labelExpression? RIGHT_PARENTHESIS durationExpression? groupByExpression? #metricAggregationExpression + | IDENTIFIER LEFT_PARENTHESIS metricExpression RIGHT_PARENTHESIS groupByExpression? #metricCallExpression ; aggregatorExpression diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java index ece6135fd6..b407a41d8e 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java @@ -28,5 +28,5 @@ default T visit(MetricSelectExpression expression) { return null; } - T visit(MetricExpression expression); + T visit(MetricAggregateExpression expression); } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpression.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricAggregateExpression.java similarity index 99% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpression.java rename to server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricAggregateExpression.java index 856ba25e2f..eab8906761 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpression.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricAggregateExpression.java @@ -56,7 +56,7 @@ * @date 2020-08-21 14:56:50 */ @Data -public class MetricExpression implements IExpression { +public class MetricAggregateExpression implements IExpression { private String from; private QueryField metric; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java index d08b99b4e6..abd3536ead 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java @@ -174,7 +174,7 @@ public IExpression visitMetricAggregationExpression(MetricExpressionParser.Metri .collect(Collectors.toCollection(LinkedHashSet::new)); } - MetricExpression expression = new MetricExpression(); + MetricAggregateExpression expression = new MetricAggregateExpression(); expression.setFrom(from); expression.setLabelSelectorExpression(whereExpression); diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java index 1ad588b6b6..f058809491 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java @@ -36,7 +36,7 @@ static class ExtendedConstantFoldingOptimizer implements IMetricExpressionVisitor { @Override - public IExpression visit(MetricExpression expression) { + public IExpression visit(MetricAggregateExpression expression) { return expression; } } @@ -74,9 +74,16 @@ public static IExpression optimize(IExpression expression) { * └── PostExpression(ConstantExpression) * */ - private static class OperatorPushingOptimizer extends AbstractOptimizer implements IMetricExpressionVisitor { + public static class OperatorPushingOptimizer extends AbstractOptimizer implements IMetricExpressionVisitor { + public static IExpression optimize(IExpression expression) { + return expression.accept(new OperatorPushingOptimizer()); + } + + private OperatorPushingOptimizer() { + } + @Override - public IExpression visit(MetricExpression expression) { + public IExpression visit(MetricAggregateExpression expression) { return expression; } @@ -85,7 +92,7 @@ public IExpression visit(ConditionalExpression expression) { IExpression lhs = expression.getLhs().accept(this); IExpression rhs = expression.getRhs().accept(this); - if (lhs instanceof MetricExpression metricExpression + if (lhs instanceof MetricAggregateExpression metricExpression && rhs instanceof LiteralExpression expectedExpression) { metricExpression.setExpected(expectedExpression); if (expression instanceof ComparisonExpression.LT) { diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricSelectExpression.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricSelectExpression.java index e8dc8367d0..a9f0b87e58 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricSelectExpression.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricSelectExpression.java @@ -62,10 +62,10 @@ public void setLabelSelectorExpression(IExpression labelSelectorExpression) { this.labelSelectorExpression = labelSelectorExpression; // The where property holds the internal expression that will be passed to the query module - this.whereText = labelSelectorExpression == null ? null : new MetricExpression.SqlStyleSerializer().serialize(labelSelectorExpression); + this.whereText = labelSelectorExpression == null ? null : new MetricAggregateExpression.SqlStyleSerializer().serialize(labelSelectorExpression); // This variable holds the raw text - this.labelSelectorText = labelSelectorExpression == null ? "" : "{" + new MetricExpression.LabelSelectorExpressionSerializer().serialize(labelSelectorExpression) + "}"; + this.labelSelectorText = labelSelectorExpression == null ? "" : "{" + new MetricAggregateExpression.LabelSelectorExpressionSerializer().serialize(labelSelectorExpression) + "}"; } @Override diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java index 861873c080..c7ca493f0c 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java @@ -26,7 +26,7 @@ import org.bithon.server.datasource.query.Interval; import org.bithon.server.datasource.query.pipeline.IQueryStep; import org.bithon.server.metric.expression.ast.IMetricExpressionVisitor; -import org.bithon.server.metric.expression.ast.MetricExpression; +import org.bithon.server.metric.expression.ast.MetricAggregateExpression; import org.bithon.server.metric.expression.ast.MetricExpressionASTBuilder; import org.bithon.server.metric.expression.ast.MetricExpressionOptimizer; import org.bithon.server.metric.expression.pipeline.step.BinaryExpressionQueryStep; @@ -88,7 +88,7 @@ public IQueryStep build(IExpression expression) { private class Builder implements IMetricExpressionVisitor { @Override - public IQueryStep visit(MetricExpression expression) { + public IQueryStep visit(MetricAggregateExpression expression) { String filterExpression = Stream.of(expression.getWhereText(), condition) .filter(Objects::nonNull) .collect(Collectors.joining(" AND ")); diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java index 7fe033e6ed..64db3f606f 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java @@ -45,7 +45,7 @@ public void test_ColonCharacter() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu:usage{appName = 'a'}) > 1"); Assertions.assertNotNull(expression); - MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + MetricAggregateExpression metricExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertEquals("jvm-metrics", metricExpression.getFrom()); Assertions.assertEquals("avg", metricExpression.getMetric().getAggregator()); Assertions.assertEquals("cpu:usage", metricExpression.getMetric().getField()); @@ -58,7 +58,7 @@ public void test_ColonCharacter() { @Test public void test_Expression() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName = 'a'}) > 1"); - MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + MetricAggregateExpression metricExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertNotNull(expression); Assertions.assertEquals("jvm-metrics", metricExpression.getFrom()); Assertions.assertEquals("avg", metricExpression.getMetric().getAggregator()); @@ -69,7 +69,7 @@ public void test_Expression() { @Test public void test_NoPredicateExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName = 'a'})"); + MetricAggregateExpression expression = (MetricAggregateExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName = 'a'})"); Assertions.assertNotNull(expression); Assertions.assertEquals("jvm-metrics", expression.getFrom()); Assertions.assertEquals("avg", expression.getMetric().getAggregator()); @@ -95,7 +95,7 @@ public void test_Percentage() { @Test public void test_WithLabelSelectorExpression() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <> 'a'}) > 1"); - MetricExpression metricSelectExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + MetricAggregateExpression metricSelectExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); Assertions.assertNotNull(expression); Assertions.assertEquals("jvm-metrics", metricSelectExpression.getFrom()); Assertions.assertEquals("avg", metricSelectExpression.getMetric().getAggregator()); @@ -115,12 +115,12 @@ public void test_DurationExpression() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5m] > 1"); Assertions.assertNotNull(expression); - MetricExpression metricSelectExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + MetricAggregateExpression metricSelectExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); Assertions.assertEquals(5, metricSelectExpression.getWindow().getDuration().toMinutes()); Assertions.assertEquals(TimeUnit.MINUTES, metricSelectExpression.getWindow().getUnit()); expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5h] > 1"); - metricSelectExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + metricSelectExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); Assertions.assertNotNull(expression); Assertions.assertEquals(5, metricSelectExpression.getWindow().getDuration().toHours()); Assertions.assertEquals(TimeUnit.HOURS, metricSelectExpression.getWindow().getUnit()); @@ -134,7 +134,7 @@ public void test_DurationExpression() { public void test_HumanReadableSizeExpression() { // binary format IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5m] > 1MiB"); - MetricExpression metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + MetricAggregateExpression metricExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); LiteralExpression expected = (LiteralExpression) ((ComparisonExpression) expression).getRhs(); Assertions.assertEquals(5, metricExpression.getWindow().getDuration().toMinutes()); @@ -144,7 +144,7 @@ public void test_HumanReadableSizeExpression() { // decimal format expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5h] > 7K"); - metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + metricExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); expected = (LiteralExpression) ((ComparisonExpression) expression).getRhs(); Assertions.assertEquals(5, metricExpression.getWindow().getDuration().toHours()); @@ -153,7 +153,7 @@ public void test_HumanReadableSizeExpression() { // simplified binary format expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5h] > 100Gi"); - metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + metricExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); expected = (LiteralExpression) ((ComparisonExpression) expression).getRhs(); Assertions.assertEquals(5, metricExpression.getWindow().getDuration().toHours()); @@ -181,7 +181,7 @@ public void test_SimplePredicateExpression() { @Test public void test_ContainsPredicateExpression() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName contains 'a'})[5m] is null"); - MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + MetricAggregateExpression metricExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertEquals("appName contains 'a'", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // contains require string literal @@ -191,7 +191,7 @@ public void test_ContainsPredicateExpression() { @Test public void test_StartsWithPredicateExpression() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName startsWith 'a'})[5m] is null"); - MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + MetricAggregateExpression metricExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertEquals("appName startsWith 'a'", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // startsWith require string literal @@ -201,7 +201,7 @@ public void test_StartsWithPredicateExpression() { @Test public void test_EnsWithPredicateExpression() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName endsWith 'a'})[5m] is null"); - MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + MetricAggregateExpression metricExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertEquals("appName endsWith 'a'", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // startsWith require string literal @@ -211,7 +211,7 @@ public void test_EnsWithPredicateExpression() { @Test public void test_hasTokenPredicateExpression() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName hasToken 'a', instanceName hasToken '192.'})[5m] > 1"); - MetricExpression metricSelectExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + MetricAggregateExpression metricSelectExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); Assertions.assertEquals("(appName hasToken 'a') AND (instanceName hasToken '192.')", metricSelectExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // hasToken require string literal @@ -221,7 +221,7 @@ public void test_hasTokenPredicateExpression() { @Test public void test_RegexMatchPredicateExpression() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{instanceName =~ '192.'})[5m] > 1"); - MetricExpression metricSelectExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + MetricAggregateExpression metricSelectExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); Assertions.assertEquals("instanceName =~ '192.'", metricSelectExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // hasToken require string literal @@ -231,7 +231,7 @@ public void test_RegexMatchPredicateExpression() { @Test public void test_RegexNotMatchPredicateExpression() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{instanceName !~ '192.'})[5m] > 1"); - MetricExpression metricSelectExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + MetricAggregateExpression metricSelectExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertEquals("instanceName !~ '192.'", metricSelectExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // hasToken require string literal @@ -242,22 +242,22 @@ public void test_RegexNotMatchPredicateExpression() { public void test_NotPredicateExpression() { // not contains IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not contains 'a'})[5m] is null"); - MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + MetricAggregateExpression metricExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertEquals("NOT (appName contains 'a')", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // not startsWith expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not startsWith 'a'})[5m] is null"); - metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + metricExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertEquals("NOT (appName startsWith 'a')", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // not endsWith expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not endsWith 'a'})[5m] is null"); - metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + metricExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertEquals("NOT (appName endsWith 'a')", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); // not hasToken expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not hasToken 'a'})[5m] is null"); - metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + metricExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertEquals("NOT (appName hasToken 'a')", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); } @@ -279,7 +279,7 @@ public void test_IsNullExpression() { @Test public void test_InExpression() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName in ('a')})[5m] is null"); - MetricExpression metricSelectExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + MetricAggregateExpression metricSelectExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertEquals("appName in ('a')", metricSelectExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName in ('a', 'b')})[5m] is null"); @@ -292,7 +292,7 @@ public void test_InExpression() { @Test public void test_NotInExpression() { IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not in ('a')})[5m] is null"); - MetricExpression metricExpression = (MetricExpression) ((BinaryExpression) expression).getLhs(); + MetricAggregateExpression metricExpression = (MetricAggregateExpression) ((BinaryExpression) expression).getLhs(); Assertions.assertEquals("appName not in ('a')", metricExpression.getLabelSelectorExpression().serializeToText(IdentifierQuotaStrategy.NONE)); MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName not in ('a', 'b')})[5m] is null"); @@ -315,7 +315,7 @@ public void test_AggregatorExpression() { @Test public void test_OffsetExpression() { - MetricExpression expression = (MetricExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName contains 'a', instanceName contains '192.'})[5m] > 1%[-7m]"); + MetricAggregateExpression expression = (MetricAggregateExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName contains 'a', instanceName contains '192.'})[5m] > 1%[-7m]"); Assertions.assertNotNull(expression.getOffset()); Assertions.assertEquals(-7, expression.getOffset().getDuration().toMinutes()); @@ -369,7 +369,7 @@ public void test_InvalidMetricName() { @Test public void test_MultipleSelector() { IExpression expression = MetricExpressionASTBuilder.parse("avg(http-metrics.responseTime{appName='a', instance='localhost', url='http://localhost/test'})[5m] > 1%[-7m]"); - MetricExpression metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + MetricAggregateExpression metricExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); IExpression whereExpression = metricExpression.getLabelSelectorExpression(); Assertions.assertEquals("(appName = 'a') AND (instance = 'localhost') AND (url = 'http://localhost/test')", whereExpression.serializeToText(IdentifierQuotaStrategy.NONE)); } @@ -377,16 +377,16 @@ public void test_MultipleSelector() { @Test public void test_ByExpression() { IExpression expression = MetricExpressionASTBuilder.parse("avg (http-metrics.responseTime{appName='a'})[5m] by (instance) > 1"); - MetricExpression metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + MetricAggregateExpression metricExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); Assertions.assertEquals(Collections.singleton("instance"), metricExpression.getGroupBy()); expression = MetricExpressionASTBuilder.parse("avg (http-metrics.responseTime{appName='a'})[5m] by (instance, url) > 1"); - metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + metricExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); Assertions.assertEquals(new HashSet<>(Arrays.asList("instance", "url")), metricExpression.getGroupBy()); expression = MetricExpressionASTBuilder.parse("avg (http-metrics.responseTime{appName='a'})[5m] by (instance, url, method) > 1"); - metricExpression = (MetricExpression) ((ComparisonExpression) expression).getLhs(); + metricExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); Assertions.assertEquals(new HashSet<>(Arrays.asList("instance", "url", "method")), metricExpression.getGroupBy()); } @@ -540,7 +540,7 @@ public void test_HybridExpression() { Assertions.assertInstanceOf(ArithmeticExpression.MUL.class, ast); ArithmeticExpression.MUL mul = (ArithmeticExpression.MUL) ast; - Assertions.assertInstanceOf(MetricExpression.class, mul.getLhs()); + Assertions.assertInstanceOf(MetricAggregateExpression.class, mul.getLhs()); Assertions.assertInstanceOf(ArithmeticExpression.SUB.class, mul.getRhs()); Assertions.assertEquals(expr, ast.serializeToText()); } diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java index 3bb9e5db65..17f913014b 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java @@ -102,8 +102,8 @@ public void test_Optimize_PushDownFilterExpression() { Assertions.assertInstanceOf(ComparisonExpression.class, ast); Assertions.assertEquals("sum(dataSource.metric) > 5", ast.serializeToText()); - ast = MetricExpressionOptimizer.optimize(ast); - Assertions.assertInstanceOf(MetricExpression.class, ast); + ast = MetricExpressionOptimizer.OperatorPushingOptimizer.optimize(ast); + Assertions.assertInstanceOf(MetricAggregateExpression.class, ast); Assertions.assertEquals("sum(dataSource.metric) > 5", ast.serializeToText()); } } From 25322090559a412b601a57a01ca0806961801261 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Mon, 2 Jun 2025 21:18:57 +0800 Subject: [PATCH 05/22] Rename to ArithmeticStep and MetricQueryStep --- .../expression/ast/MetricCallExpression.java | 32 +++++++++ .../pipeline/QueryPipelineBuilder.java | 68 +++++++++---------- ...sionQueryStep.java => ArithmeticStep.java} | 18 ++--- ...ionQueryStep.java => MetricQueryStep.java} | 6 +- ...yStepTest.java => ArithmeticStepTest.java} | 2 +- 5 files changed, 79 insertions(+), 47 deletions(-) create mode 100644 server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricCallExpression.java rename server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/{BinaryExpressionQueryStep.java => ArithmeticStep.java} (95%) rename server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/{MetricExpressionQueryStep.java => MetricQueryStep.java} (95%) rename server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/{BinaryExpressionQueryStepTest.java => ArithmeticStepTest.java} (99%) diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricCallExpression.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricCallExpression.java new file mode 100644 index 0000000000..0d35388fb2 --- /dev/null +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricCallExpression.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.metric.expression.ast; + + +import org.bithon.component.commons.expression.IExpression; + +import java.util.Set; + +/** + * @author frank.chen021@outlook.com + * @date 2/6/25 8:33 pm + */ +public class MetricCallExpression { + private String operator; + private IExpression expression; + private Set groupBy; +} diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java index c7ca493f0c..1d8ccd435e 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java @@ -29,10 +29,10 @@ import org.bithon.server.metric.expression.ast.MetricAggregateExpression; import org.bithon.server.metric.expression.ast.MetricExpressionASTBuilder; import org.bithon.server.metric.expression.ast.MetricExpressionOptimizer; -import org.bithon.server.metric.expression.pipeline.step.BinaryExpressionQueryStep; +import org.bithon.server.metric.expression.pipeline.step.ArithmeticStep; import org.bithon.server.metric.expression.pipeline.step.FilterStep; import org.bithon.server.metric.expression.pipeline.step.LiteralQueryStep; -import org.bithon.server.metric.expression.pipeline.step.MetricExpressionQueryStep; +import org.bithon.server.metric.expression.pipeline.step.MetricQueryStep; import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; import org.bithon.server.web.service.datasource.api.QueryField; @@ -97,33 +97,33 @@ public IQueryStep visit(MetricAggregateExpression expression) { // Create expression as: ( current - base ) / base QueryField metricField = expression.getMetric(); String expr = StringUtils.format("%s(%s) * 1.0", metricField.getAggregator(), metricField.getField()); - MetricExpressionQueryStep curr = new MetricExpressionQueryStep(QueryRequest.builder() - .dataSource(expression.getFrom()) - .filterExpression(filterExpression) - .groupBy(expression.getGroupBy()) - .fields(List.of(new QueryField(metricField.getName(), metricField.getField(), null, expr))) - .interval(intervalRequest) - .build(), - dataSourceApi); - - MetricExpressionQueryStep base = new MetricExpressionQueryStep(QueryRequest.builder() - .dataSource(expression.getFrom()) - .filterExpression(filterExpression) - .groupBy(expression.getGroupBy()) - .fields(List.of(new QueryField( + MetricQueryStep curr = new MetricQueryStep(QueryRequest.builder() + .dataSource(expression.getFrom()) + .filterExpression(filterExpression) + .groupBy(expression.getGroupBy()) + .fields(List.of(new QueryField(metricField.getName(), metricField.getField(), null, expr))) + .interval(intervalRequest) + .build(), + dataSourceApi); + + MetricQueryStep base = new MetricQueryStep(QueryRequest.builder() + .dataSource(expression.getFrom()) + .filterExpression(filterExpression) + .groupBy(expression.getGroupBy()) + .fields(List.of(new QueryField( // Use offset AS the output name expression.getOffset().toString(), metricField.getField(), null, expr))) - .interval(intervalRequest) - .offset(expression.getOffset()) - .build(), - dataSourceApi); + .interval(intervalRequest) + .offset(expression.getOffset()) + .build(), + dataSourceApi); // - return new BinaryExpressionQueryStep.Div( - new BinaryExpressionQueryStep.Sub( + return new ArithmeticStep.Div( + new ArithmeticStep.Sub( curr, base, "diff", @@ -138,14 +138,14 @@ public IQueryStep visit(MetricAggregateExpression expression) { metricField.getName(), expression.getOffset().toString() ); } else { - return new MetricExpressionQueryStep(QueryRequest.builder() - .dataSource(expression.getFrom()) - .filterExpression(filterExpression) - .groupBy(expression.getGroupBy()) - .fields(List.of(expression.getMetric())) - .interval(intervalRequest) - .build(), - dataSourceApi); + return new MetricQueryStep(QueryRequest.builder() + .dataSource(expression.getFrom()) + .filterExpression(filterExpression) + .groupBy(expression.getGroupBy()) + .fields(List.of(expression.getMetric())) + .interval(intervalRequest) + .build(), + dataSourceApi); } } @@ -160,10 +160,10 @@ public IQueryStep visit(LiteralExpression expression) { @Override public IQueryStep visit(ArithmeticExpression expression) { return switch (expression.getType()) { - case "+" -> new BinaryExpressionQueryStep.Add(expression.getLhs().accept(this), expression.getRhs().accept(this)); - case "-" -> new BinaryExpressionQueryStep.Sub(expression.getLhs().accept(this), expression.getRhs().accept(this)); - case "*" -> new BinaryExpressionQueryStep.Mul(expression.getLhs().accept(this), expression.getRhs().accept(this)); - case "/" -> new BinaryExpressionQueryStep.Div(expression.getLhs().accept(this), expression.getRhs().accept(this)); + case "+" -> new ArithmeticStep.Add(expression.getLhs().accept(this), expression.getRhs().accept(this)); + case "-" -> new ArithmeticStep.Sub(expression.getLhs().accept(this), expression.getRhs().accept(this)); + case "*" -> new ArithmeticStep.Mul(expression.getLhs().accept(this), expression.getRhs().accept(this)); + case "/" -> new ArithmeticStep.Div(expression.getLhs().accept(this), expression.getRhs().accept(this)); default -> throw new UnsupportedOperationException("Unsupported arithmetic expression: " + expression.getType()); }; } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java similarity index 95% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStep.java rename to server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java index 82f06a981a..cf2b099872 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java @@ -35,7 +35,7 @@ * @author frank.chen021@outlook.com * @date 4/4/25 3:49 pm */ -public abstract class BinaryExpressionQueryStep implements IQueryStep { +public abstract class ArithmeticStep implements IQueryStep { private final IQueryStep lhs; private final IQueryStep rhs; private final String resultColumnName; @@ -45,7 +45,7 @@ public abstract class BinaryExpressionQueryStep implements IQueryStep { */ private final Set retainedColumns; - public static class Add extends BinaryExpressionQueryStep { + public static class Add extends ArithmeticStep { public Add(IQueryStep left, IQueryStep right) { super(left, right, null); } @@ -56,7 +56,7 @@ int getOperatorIndex() { } } - public static class Sub extends BinaryExpressionQueryStep { + public static class Sub extends ArithmeticStep { public Sub(IQueryStep left, IQueryStep right) { super(left, right, null); } @@ -74,7 +74,7 @@ int getOperatorIndex() { } } - public static class Mul extends BinaryExpressionQueryStep { + public static class Mul extends ArithmeticStep { public Mul(IQueryStep left, IQueryStep right) { super(left, right, null); } @@ -85,7 +85,7 @@ int getOperatorIndex() { } } - public static class Div extends BinaryExpressionQueryStep { + public static class Div extends ArithmeticStep { public Div(IQueryStep left, IQueryStep right) { super(left, right, null); } @@ -103,10 +103,10 @@ int getOperatorIndex() { } } - protected BinaryExpressionQueryStep(IQueryStep left, - IQueryStep right, - String resultColumnName, - String... retainedColumns) { + protected ArithmeticStep(IQueryStep left, + IQueryStep right, + String resultColumnName, + String... retainedColumns) { this.lhs = left; this.rhs = right; this.resultColumnName = resultColumnName == null ? "value" : resultColumnName; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricExpressionQueryStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java similarity index 95% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricExpressionQueryStep.java rename to server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java index 4eae3bdf63..e3515a17c6 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricExpressionQueryStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java @@ -37,7 +37,7 @@ * @author frank.chen021@outlook.com * @date 4/4/25 3:48 pm */ -public class MetricExpressionQueryStep implements IQueryStep { +public class MetricQueryStep implements IQueryStep { private final QueryRequest queryRequest; private final IDataSourceApi dataSourceApi; private final boolean isScalar; @@ -45,8 +45,8 @@ public class MetricExpressionQueryStep implements IQueryStep { // Make sure the evaluation is executed ONLY ONCE when the expression is referenced multiple times private volatile CompletableFuture cachedResponse; - public MetricExpressionQueryStep(QueryRequest queryRequest, - IDataSourceApi dataSourceApi) { + public MetricQueryStep(QueryRequest queryRequest, + IDataSourceApi dataSourceApi) { this.queryRequest = queryRequest; this.dataSourceApi = dataSourceApi; this.isScalar = computeIsScalar(queryRequest); diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java similarity index 99% rename from server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStepTest.java rename to server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java index 607de5e96a..89366f86e6 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/BinaryExpressionQueryStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java @@ -41,7 +41,7 @@ * @date 4/4/25 9:27 pm */ @SuppressWarnings("PointlessArithmeticExpression") -public class BinaryExpressionQueryStepTest { +public class ArithmeticStepTest { private IDataSourceApi dataSourceApi; @BeforeEach From e0550bd188ebe319018272b4be395522871a2d52 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Mon, 2 Jun 2025 22:22:00 +0800 Subject: [PATCH 06/22] Add expected/function call --- .../metric/expression/MetricExpression.g4 | 12 ++- .../metric/expression/ast/AggregatorEnum.java | 9 ++ .../ast/IMetricExpressionVisitor.java | 5 ++ .../expression/ast/MetricCallExpression.java | 32 ------- .../ast/MetricExpectedExpression.java | 85 +++++++++++++++++++ .../ast/MetricExpressionASTBuilder.java | 42 +++------ .../ast/MetricExpressionOptimizer.java | 10 +++ .../pipeline/QueryPipelineBuilder.java | 50 ++++++++--- .../expression/pipeline/step/FilterStep.java | 19 +++-- .../pipeline/step/FunctionStep.java | 54 ++++++++++++ .../ast/MetricExpressionASTBuilderTest.java | 20 ++--- .../service/datasource/api/QueryRequest.java | 5 ++ 12 files changed, 249 insertions(+), 94 deletions(-) delete mode 100644 server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricCallExpression.java create mode 100644 server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpectedExpression.java create mode 100644 server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionStep.java diff --git a/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 b/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 index eda03d950e..eb4f38a4a3 100644 --- a/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 +++ b/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 @@ -12,12 +12,16 @@ metricExpression : metricExpression (MUL|DIV) metricExpression #arithmeticExpression | metricExpression (ADD|SUB) metricExpression #arithmeticExpression | '(' metricExpression ')' #parenthesisMetricExpression - | numberLiteralExpression #metricLiteralExpression // This allows to use literal expression in arithmetic expression - | metricQNameExpression labelExpression? durationExpression? #metricSelectExpression + | numberLiteralExpression #metricLiteralExpression // This allows to use literal expression in arithmetic expression + | metricSelectExpressionDecl #metricSelectExpression | metricExpression metricPredicateExpression metricExpectedExpression #metricFilterExpression // Legacy metric aggregation expression - | aggregatorExpression LEFT_PARENTHESIS metricQNameExpression labelExpression? RIGHT_PARENTHESIS durationExpression? groupByExpression? #metricAggregationExpression - | IDENTIFIER LEFT_PARENTHESIS metricExpression RIGHT_PARENTHESIS groupByExpression? #metricCallExpression + | aggregatorExpression LEFT_PARENTHESIS metricSelectExpressionDecl RIGHT_PARENTHESIS durationExpression? groupByExpression? #metricAggregationExpression + | IDENTIFIER LEFT_PARENTHESIS metricExpression (',' metricExpression)* RIGHT_PARENTHESIS groupByExpression? #functionCallExpression + ; + +metricSelectExpressionDecl + : metricQNameExpression labelExpression? durationExpression? ; aggregatorExpression diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/AggregatorEnum.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/AggregatorEnum.java index 2555b5b3d0..1d9ec598b8 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/AggregatorEnum.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/AggregatorEnum.java @@ -69,4 +69,13 @@ public boolean isColumnSupported(IColumn column) { }; public abstract boolean isColumnSupported(IColumn column); + + public static AggregatorEnum fromString(String name) { + for (AggregatorEnum aggregator : values()) { + if (aggregator.name().equalsIgnoreCase(name)) { + return aggregator; + } + } + return null; + } } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java index b407a41d8e..381dc39452 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/IMetricExpressionVisitor.java @@ -28,5 +28,10 @@ default T visit(MetricSelectExpression expression) { return null; } + default T visit(MetricExpectedExpression expression) { + return null; + } + T visit(MetricAggregateExpression expression); + } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricCallExpression.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricCallExpression.java deleted file mode 100644 index 0d35388fb2..0000000000 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricCallExpression.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2020 bithon.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bithon.server.metric.expression.ast; - - -import org.bithon.component.commons.expression.IExpression; - -import java.util.Set; - -/** - * @author frank.chen021@outlook.com - * @date 2/6/25 8:33 pm - */ -public class MetricCallExpression { - private String operator; - private IExpression expression; - private Set groupBy; -} diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpectedExpression.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpectedExpression.java new file mode 100644 index 0000000000..ac2b2d513f --- /dev/null +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpectedExpression.java @@ -0,0 +1,85 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.metric.expression.ast; + + +import lombok.Builder; +import lombok.Getter; +import org.bithon.component.commons.expression.IDataType; +import org.bithon.component.commons.expression.IEvaluationContext; +import org.bithon.component.commons.expression.IExpression; +import org.bithon.component.commons.expression.IExpressionInDepthVisitor; +import org.bithon.component.commons.expression.IExpressionVisitor; +import org.bithon.component.commons.expression.LiteralExpression; +import org.bithon.component.commons.expression.serialization.ExpressionSerializer; +import org.bithon.component.commons.utils.HumanReadableDuration; + +import javax.annotation.Nullable; + +/** + * @author frank.chen021@outlook.com + * @date 2/6/25 10:03 pm + */ +@Getter +@Builder +public class MetricExpectedExpression implements IExpression { + /** + * The expected value of the metric. + */ + private final LiteralExpression expected; + + @Nullable + private final HumanReadableDuration offset; + + @Override + public IDataType getDataType() { + return expected.getDataType(); + } + + @Override + public String getType() { + return "expected"; + } + + @Override + public Object evaluate(IEvaluationContext context) { + throw new UnsupportedOperationException(); + } + + @Override + public void accept(IExpressionInDepthVisitor visitor) { + } + + @Override + public T accept(IExpressionVisitor visitor) { + if (visitor instanceof IMetricExpressionVisitor metricVisitor) { + return metricVisitor.visit(this); + } else { + throw new UnsupportedOperationException("Unsupported visitor type: " + visitor.getClass().getName()); + } + } + + @Override + public void serializeToText(ExpressionSerializer serializer) { + expected.serializeToText(serializer); + if (offset != null) { + serializer.append("["); + serializer.append(offset.toString()); + serializer.append("]"); + } + } +} diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java index abd3536ead..67afc419d2 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java @@ -125,18 +125,14 @@ public IExpression visitParenthesisMetricExpression(MetricExpressionParser.Paren @Override public IExpression visitMetricAggregationExpression(MetricExpressionParser.MetricAggregationExpressionContext ctx) { - String[] names = ctx.metricQNameExpression().getText().split("\\."); - String from = names[0]; - String metric = names[1]; - String aggregatorText = ctx.aggregatorExpression().getText().toLowerCase(Locale.ENGLISH); - AggregatorEnum aggregator; - try { - aggregator = AggregatorEnum.valueOf(aggregatorText); - } catch (RuntimeException ignored) { + AggregatorEnum aggregator = AggregatorEnum.fromString(aggregatorText); + if (aggregator == null) { throw new InvalidExpressionException(StringUtils.format("The aggregator [%s] in the expression is not supported", aggregatorText)); } + MetricSelectExpression metricSelectExpression = (MetricSelectExpression) ctx.metricSelectExpressionDecl().accept(this); + HumanReadableDuration duration = null; MetricExpressionParser.DurationExpressionContext windowExpressionCtx = ctx.durationExpression(); if (windowExpressionCtx != null) { @@ -149,21 +145,6 @@ public IExpression visitMetricAggregationExpression(MetricExpressionParser.Metri } } - IExpression whereExpression = null; - MetricExpressionParser.LabelExpressionContext where = ctx.labelExpression(); - if (where != null) { - LabelSelectorExpressionBuilder filterASTBuilder = new LabelSelectorExpressionBuilder(); - List filters = where.labelSelectorExpression() - .stream() - .map((filter) -> filter.accept(filterASTBuilder)) - .collect(Collectors.toList()); - if (filters.size() == 1) { - whereExpression = filters.get(0); - } else if (filters.size() > 1) { - whereExpression = new LogicalExpression.AND(filters); - } - } - Set groupBy = null; MetricExpressionParser.GroupByExpressionContext groupByExpression = ctx.groupByExpression(); if (groupByExpression != null) { @@ -175,19 +156,19 @@ public IExpression visitMetricAggregationExpression(MetricExpressionParser.Metri } MetricAggregateExpression expression = new MetricAggregateExpression(); - expression.setFrom(from); - expression.setLabelSelectorExpression(whereExpression); + expression.setFrom(metricSelectExpression.getFrom()); + expression.setLabelSelectorExpression(metricSelectExpression.getLabelSelectorExpression()); // For 'count' aggregator, use the 'count' as output column instead of using the column name as output name - expression.setMetric(new QueryField(aggregator.equals(AggregatorEnum.count) ? "count" : metric, metric, aggregator.name())); + expression.setMetric(new QueryField(aggregator.equals(AggregatorEnum.count) ? "count" : metricSelectExpression.getMetric(), metricSelectExpression.getMetric(), aggregator.name())); expression.setGroupBy(groupBy); - expression.setWindow(duration); + expression.setWindow(duration == null ? metricSelectExpression.getWindow() : duration); return expression; } @Override - public IExpression visitMetricSelectExpression(MetricExpressionParser.MetricSelectExpressionContext ctx) { + public IExpression visitMetricSelectExpressionDecl(MetricExpressionParser.MetricSelectExpressionDeclContext ctx) { String[] names = ctx.metricQNameExpression().getText().split("\\."); String from = names[0]; String metric = names[1]; @@ -259,7 +240,10 @@ public IExpression visitMetricFilterExpression(MetricExpressionParser.MetricFilt expected, offset); - return predicate.createComparisonExpression(lhs, expected); + return predicate.createComparisonExpression(lhs, MetricExpectedExpression.builder() + .expected(expected) + .offset(offset) + .build()); } private static PredicateEnum getPredicate(int predicateToken, diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java index f058809491..5cb7218f55 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java @@ -39,6 +39,16 @@ static class ExtendedConstantFoldingOptimizer public IExpression visit(MetricAggregateExpression expression) { return expression; } + + @Override + public IExpression visit(MetricSelectExpression expression) { + return expression; + } + + @Override + public IExpression visit(MetricExpectedExpression expression) { + return expression; + } } public static IExpression optimize(IExpression expression) { diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java index 1d8ccd435e..f63ca2c718 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java @@ -27,6 +27,7 @@ import org.bithon.server.datasource.query.pipeline.IQueryStep; import org.bithon.server.metric.expression.ast.IMetricExpressionVisitor; import org.bithon.server.metric.expression.ast.MetricAggregateExpression; +import org.bithon.server.metric.expression.ast.MetricExpectedExpression; import org.bithon.server.metric.expression.ast.MetricExpressionASTBuilder; import org.bithon.server.metric.expression.ast.MetricExpressionOptimizer; import org.bithon.server.metric.expression.pipeline.step.ArithmeticStep; @@ -111,11 +112,11 @@ public IQueryStep visit(MetricAggregateExpression expression) { .filterExpression(filterExpression) .groupBy(expression.getGroupBy()) .fields(List.of(new QueryField( - // Use offset AS the output name - expression.getOffset().toString(), - metricField.getField(), - null, - expr))) + // Use offset AS the output name + expression.getOffset().toString(), + metricField.getField(), + null, + expr))) .interval(intervalRequest) .offset(expression.getOffset()) .build(), @@ -149,6 +150,34 @@ public IQueryStep visit(MetricAggregateExpression expression) { } } + /* + @Override + public IQueryStep visit(FunctionCallExpression expression) { + if (expression.getArguments() instanceof MetricSelectExpression selectExpression) { + AggregatorEnum aggregator = AggregatorEnum.fromString(expression.getOperator()); + if (aggregator != null) { + String filterExpression = Stream.of(selectExpression.getWhereText(), condition) + .filter(Objects::nonNull) + .collect(Collectors.joining(" AND ")); + return new MetricQueryStep(QueryRequest.builder() + .dataSource(selectExpression.getFrom()) + .filterExpression(filterExpression) + .groupBy(expression.getGroupBy()) + .fields(List.of(new QueryField(selectExpression.getMetric(), + selectExpression.getMetric(), + aggregator.name()))) + .interval(intervalRequest) + .build(), + dataSourceApi); + } else { + // other functions + throw new UnsupportedOperationException("Unsupported metric call expression: " + expression.getOperator()); + } + } else { + throw new UnsupportedOperationException("Unsupported metric call expression: " + expression.getArguments().getClass().getSimpleName()); + } + }*/ + @Override public IQueryStep visit(LiteralExpression expression) { return new LiteralQueryStep(expression, Interval.of(intervalRequest.getStartISO8601(), @@ -171,34 +200,35 @@ public IQueryStep visit(ArithmeticExpression expression) { @Override public IQueryStep visit(ConditionalExpression expression) { IQueryStep source = expression.getLhs().accept(this); + if (expression instanceof ComparisonExpression.LT) { return new FilterStep.LT( source, - (LiteralExpression) expression.getRhs() + (MetricExpectedExpression) expression.getRhs() ); } if (expression instanceof ComparisonExpression.LTE) { return new FilterStep.LTE( source, - (LiteralExpression) expression.getRhs() + (MetricExpectedExpression) expression.getRhs() ); } if (expression instanceof ComparisonExpression.GT) { return new FilterStep.GT( source, - (LiteralExpression) expression.getRhs() + (MetricExpectedExpression) expression.getRhs() ); } if (expression instanceof ComparisonExpression.GTE) { return new FilterStep.GTE( source, - (LiteralExpression) expression.getRhs() + (MetricExpectedExpression) expression.getRhs() ); } if (expression instanceof ComparisonExpression.NE) { return new FilterStep.NE( source, - (LiteralExpression) expression.getRhs() + (MetricExpectedExpression) expression.getRhs() ); } throw new UnsupportedOperationException("Unsupported conditional expression: " + expression.getType()); diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java index 91e5750c03..97401877e9 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java @@ -23,6 +23,7 @@ import org.bithon.server.datasource.query.pipeline.ColumnarTable; import org.bithon.server.datasource.query.pipeline.IQueryStep; import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; +import org.bithon.server.metric.expression.ast.MetricExpectedExpression; import java.util.concurrent.CompletableFuture; @@ -32,9 +33,9 @@ */ public abstract class FilterStep implements IQueryStep { protected final IQueryStep source; - protected final LiteralExpression expected; + protected final MetricExpectedExpression expected; - protected FilterStep(IQueryStep source, LiteralExpression expected) { + protected FilterStep(IQueryStep source, MetricExpectedExpression expected) { this.source = source; this.expected = expected; } @@ -54,7 +55,7 @@ public CompletableFuture execute() throws Exception { int filteredRowCount = 0; int[] filteredRows = new int[result.getTable().rowCount()]; if (column.getDataType() == IDataType.LONG) { - long expectedValue = ((Number) expected.getValue()).longValue(); + long expectedValue = ((Number) expected.getExpected().getValue()).longValue(); for (int i = 0, size = column.size(); i < size; i++) { if (filter(column.getLong(i), expectedValue)) { @@ -62,7 +63,7 @@ public CompletableFuture execute() throws Exception { } } } else if (column.getDataType() == IDataType.DOUBLE) { - double expectedValue = ((Number) expected.getValue()).doubleValue(); + double expectedValue = ((Number) expected.getExpected().getValue()).doubleValue(); for (int i = 0, size = column.size(); i < size; i++) { if (filter(column.getDouble(i), expectedValue)) { @@ -95,7 +96,7 @@ public CompletableFuture execute() throws Exception { protected abstract boolean filter(double actual, double expected); public static class LT extends FilterStep { - public LT(IQueryStep source, LiteralExpression expected) { + public LT(IQueryStep source, MetricExpectedExpression expected) { super(source, expected); } @@ -111,7 +112,7 @@ protected boolean filter(double actual, double expected) { } public static class LTE extends FilterStep { - public LTE(IQueryStep source, LiteralExpression expected) { + public LTE(IQueryStep source, MetricExpectedExpression expected) { super(source, expected); } @@ -127,7 +128,7 @@ protected boolean filter(double actual, double expected) { } public static class GT extends FilterStep { - public GT(IQueryStep source, LiteralExpression expected) { + public GT(IQueryStep source, MetricExpectedExpression expected) { super(source, expected); } @@ -143,7 +144,7 @@ protected boolean filter(double actual, double expected) { } public static class GTE extends FilterStep { - public GTE(IQueryStep source, LiteralExpression expected) { + public GTE(IQueryStep source, MetricExpectedExpression expected) { super(source, expected); } @@ -159,7 +160,7 @@ protected boolean filter(double actual, double expected) { } public static class NE extends FilterStep { - public NE(IQueryStep source, LiteralExpression expected) { + public NE(IQueryStep source, MetricExpectedExpression expected) { super(source, expected); } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionStep.java new file mode 100644 index 0000000000..4404ac0472 --- /dev/null +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionStep.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.metric.expression.pipeline.step; + + +import org.bithon.server.datasource.query.pipeline.IQueryStep; +import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +/** + * @author frank.chen021@outlook.com + * @date 2/6/25 9:29 pm + */ +public abstract class FunctionStep implements IQueryStep { + private final IQueryStep source; + + public FunctionStep(IQueryStep source) { + this.source = source; + } + + @Override + public boolean isScalar() { + return source.isScalar(); + } + + public static FunctionStep apply(IQueryStep source, Function function) { + return new FunctionStep(source) { + @Override + public CompletableFuture execute() throws Exception { + return source.execute().thenApply(function); + } + }; + } + + public static Function SUM = (result) -> { + return result; + }; +} diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java index 64db3f606f..0037e118ea 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java @@ -135,29 +135,29 @@ public void test_HumanReadableSizeExpression() { // binary format IExpression expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5m] > 1MiB"); MetricAggregateExpression metricExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); - LiteralExpression expected = (LiteralExpression) ((ComparisonExpression) expression).getRhs(); + MetricExpectedExpression expected = (MetricExpectedExpression) ((ComparisonExpression) expression).getRhs(); Assertions.assertEquals(5, metricExpression.getWindow().getDuration().toMinutes()); Assertions.assertEquals(TimeUnit.MINUTES, metricExpression.getWindow().getUnit()); - Assertions.assertEquals(HumanReadableNumber.of("1MiB"), expected.getValue()); + Assertions.assertEquals(HumanReadableNumber.of("1MiB"), expected.getExpected().getValue()); Assertions.assertEquals("avg(jvm-metrics.cpu{appName <= \"a\"})[5m] > 1MiB", expression.serializeToText()); // decimal format expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5h] > 7K"); metricExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); - expected = (LiteralExpression) ((ComparisonExpression) expression).getRhs(); + expected = (MetricExpectedExpression) ((ComparisonExpression) expression).getRhs(); Assertions.assertEquals(5, metricExpression.getWindow().getDuration().toHours()); - Assertions.assertEquals(HumanReadableNumber.of("7K"), expected.getValue()); + Assertions.assertEquals(HumanReadableNumber.of("7K"), expected.getExpected().getValue()); Assertions.assertEquals("avg(jvm-metrics.cpu{appName <= \"a\"})[5h] > 7K", expression.serializeToText()); // simplified binary format expression = MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5h] > 100Gi"); metricExpression = (MetricAggregateExpression) ((ComparisonExpression) expression).getLhs(); - expected = (LiteralExpression) ((ComparisonExpression) expression).getRhs(); + expected = (MetricExpectedExpression) ((ComparisonExpression) expression).getRhs(); Assertions.assertEquals(5, metricExpression.getWindow().getDuration().toHours()); - Assertions.assertEquals(HumanReadableNumber.of("100Gi"), expected.getValue()); + Assertions.assertEquals(HumanReadableNumber.of("100Gi"), expected.getExpected().getValue()); Assertions.assertEquals("avg(jvm-metrics.cpu{appName <= \"a\"})[5h] > 100Gi", expression.serializeToText()); // Invalid human-readable size @@ -166,7 +166,7 @@ public void test_HumanReadableSizeExpression() { @Test public void test_SimplePredicateExpression() { - MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5m] > 1"); + MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'}[5m]) > 1"); MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5m] >= 1"); MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName <= 'a'})[5m] = 1"); @@ -315,9 +315,9 @@ public void test_AggregatorExpression() { @Test public void test_OffsetExpression() { - MetricAggregateExpression expression = (MetricAggregateExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName contains 'a', instanceName contains '192.'})[5m] > 1%[-7m]"); - Assertions.assertNotNull(expression.getOffset()); - Assertions.assertEquals(-7, expression.getOffset().getDuration().toMinutes()); + ComparisonExpression expression = (ComparisonExpression) MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName contains 'a', instanceName contains '192.'})[5m] > 1%[-7m]"); + //Assertions.assertNotNull(expression.getOffset()); + //Assertions.assertEquals(-7, expression.getOffset().getDuration().toMinutes()); // Only percentage is supported now Assertions.assertThrows(InvalidExpressionException.class, () -> MetricExpressionASTBuilder.parse("avg(jvm-metrics.cpu{appName contains 'a', instanceName contains '192.'})[5m] > 1[0m]")); diff --git a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/QueryRequest.java b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/QueryRequest.java index 99c032e781..c9bcec4e96 100644 --- a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/QueryRequest.java +++ b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/QueryRequest.java @@ -74,5 +74,10 @@ public class QueryRequest { @Nullable private HumanReadableDuration offset; + /** + * filter expression applied after query aggregation + */ + private String postFilterExpression; + private Query.ResultFormat resultFormat; } From 1c12e10398fe86a8a29ccd7692d25ab8f2a1b35c Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Tue, 3 Jun 2025 22:03:54 +0800 Subject: [PATCH 07/22] Fix check --- .../server/metric/expression/pipeline/step/FilterStep.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java index 97401877e9..6006fbccc2 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java @@ -18,7 +18,6 @@ import org.bithon.component.commons.expression.IDataType; -import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.server.datasource.query.pipeline.Column; import org.bithon.server.datasource.query.pipeline.ColumnarTable; import org.bithon.server.datasource.query.pipeline.IQueryStep; From 5cf8e1994da1c920f2ec5edfa26dbee572b1c2b9 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 4 Jun 2025 21:29:08 +0800 Subject: [PATCH 08/22] add function call step --- dev/checkstyle-suppressions.xml | 1 + pom.xml | 9 +++++++ .../metric/expression/MetricExpression.g4 | 2 +- .../ast/MetricExpressionASTBuilder.java | 18 ++++++++++--- .../pipeline/QueryPipelineBuilder.java | 4 +-- .../expression/pipeline/step/FilterStep.java | 2 ++ .../pipeline/step/FunctionCallStep.java | 26 +++++++++++++++++++ .../ast/MetricExpressionASTBuilderTest.java | 12 ++++++++- 8 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java diff --git a/dev/checkstyle-suppressions.xml b/dev/checkstyle-suppressions.xml index be51b28a8f..4e2a9b611c 100644 --- a/dev/checkstyle-suppressions.xml +++ b/dev/checkstyle-suppressions.xml @@ -33,6 +33,7 @@ + diff --git a/pom.xml b/pom.xml index 8ee0a9c425..f04e6a440a 100644 --- a/pom.xml +++ b/pom.xml @@ -129,6 +129,15 @@ org.apache.maven.plugins maven-compiler-plugin 3.14.0 + + + + org.projectlombok + lombok + ${lombok.version} + + + diff --git a/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 b/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 index eb4f38a4a3..0ff12f45a5 100644 --- a/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 +++ b/server/metric-expression/src/main/antlr4/org/bithon/server/metric/expression/MetricExpression.g4 @@ -17,7 +17,7 @@ metricExpression | metricExpression metricPredicateExpression metricExpectedExpression #metricFilterExpression // Legacy metric aggregation expression | aggregatorExpression LEFT_PARENTHESIS metricSelectExpressionDecl RIGHT_PARENTHESIS durationExpression? groupByExpression? #metricAggregationExpression - | IDENTIFIER LEFT_PARENTHESIS metricExpression (',' metricExpression)* RIGHT_PARENTHESIS groupByExpression? #functionCallExpression + | IDENTIFIER LEFT_PARENTHESIS metricExpression (',' metricExpression)* RIGHT_PARENTHESIS #functionCallExpression ; metricSelectExpressionDecl diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java index 67afc419d2..5e6fd61f01 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java @@ -25,6 +25,7 @@ import org.bithon.component.commons.expression.ComparisonExpression; import org.bithon.component.commons.expression.ConditionalExpression; import org.bithon.component.commons.expression.ExpressionList; +import org.bithon.component.commons.expression.FunctionExpression; import org.bithon.component.commons.expression.IDataType; import org.bithon.component.commons.expression.IExpression; import org.bithon.component.commons.expression.IdentifierExpression; @@ -128,11 +129,10 @@ public IExpression visitMetricAggregationExpression(MetricExpressionParser.Metri String aggregatorText = ctx.aggregatorExpression().getText().toLowerCase(Locale.ENGLISH); AggregatorEnum aggregator = AggregatorEnum.fromString(aggregatorText); if (aggregator == null) { - throw new InvalidExpressionException(StringUtils.format("The aggregator [%s] in the expression is not supported", aggregatorText)); + return new FunctionExpression(aggregatorText, ctx.metricSelectExpressionDecl().accept(this)); } MetricSelectExpression metricSelectExpression = (MetricSelectExpression) ctx.metricSelectExpressionDecl().accept(this); - HumanReadableDuration duration = null; MetricExpressionParser.DurationExpressionContext windowExpressionCtx = ctx.durationExpression(); if (windowExpressionCtx != null) { @@ -246,6 +246,17 @@ public IExpression visitMetricFilterExpression(MetricExpressionParser.MetricFilt .build()); } + @Override + public IExpression visitFunctionCallExpression(MetricExpressionParser.FunctionCallExpressionContext ctx) { + String function = ctx.IDENTIFIER().getSymbol().getText(); + IExpression[] arguments = ctx.metricExpression() + .stream() + .map((metricExpression) -> metricExpression.accept(this)) + .toList() + .toArray(new IExpression[0]); + return new FunctionExpression(function, arguments); + } + private static PredicateEnum getPredicate(int predicateToken, LiteralExpression expected, HumanReadableDuration expectedWindow) { @@ -394,7 +405,8 @@ private static LiteralExpression toLiteralASTExpression(int tokenType, String return switch (tokenType) { case MetricExpressionParser.DECIMAL_LITERAL -> LiteralExpression.ofDecimal(parseDecimal(text)); case MetricExpressionParser.INTEGER_LITERAL -> LiteralExpression.ofLong(Integer.parseInt(text)); - case MetricExpressionParser.PERCENTAGE_LITERAL -> LiteralExpression.of(new HumanReadablePercentage(text)); + case MetricExpressionParser.PERCENTAGE_LITERAL -> + LiteralExpression.of(new HumanReadablePercentage(text)); case MetricExpressionParser.STRING_LITERAL -> { String input = text; if (!input.isEmpty()) { diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java index f63ca2c718..bc5554c08a 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java @@ -77,7 +77,7 @@ public IQueryStep build(String expression) { IExpression expr = MetricExpressionASTBuilder.parse(expression); // Apply optimization like constant folding on parsed expression - // The optimization is applied here so that above parse can be tested separately + // The optimization is applied here so that the above parse can be tested separately expr = MetricExpressionOptimizer.optimize(expr); return this.build(expr); @@ -95,7 +95,7 @@ public IQueryStep visit(MetricAggregateExpression expression) { .collect(Collectors.joining(" AND ")); if (expression.getOffset() != null) { - // Create expression as: ( current - base ) / base + // Create expression as: (current - base) / base QueryField metricField = expression.getMetric(); String expr = StringUtils.format("%s(%s) * 1.0", metricField.getAggregator(), metricField.getField()); MetricQueryStep curr = new MetricQueryStep(QueryRequest.builder() diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java index 6006fbccc2..8b671dd61f 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java @@ -27,6 +27,8 @@ import java.util.concurrent.CompletableFuture; /** + * Plan for the {@link org.bithon.component.commons.expression.ComparisonExpression} + * * @author frank.chen021@outlook.com * @date 1/6/25 9:06 pm */ diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java new file mode 100644 index 0000000000..144d6c5f04 --- /dev/null +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.metric.expression.pipeline.step; + +/** + * Plan for the {@link org.bithon.component.commons.expression.FunctionExpression} + * + * @author frank.chen021@outlook.com + * @date 2025/6/4 21:28 + */ +public class FunctionCallStep { +} diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java index 0037e118ea..99fbbb021b 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilderTest.java @@ -19,6 +19,7 @@ import org.bithon.component.commons.expression.ArithmeticExpression; import org.bithon.component.commons.expression.BinaryExpression; import org.bithon.component.commons.expression.ComparisonExpression; +import org.bithon.component.commons.expression.FunctionExpression; import org.bithon.component.commons.expression.IExpression; import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.expression.expt.InvalidExpressionException; @@ -533,7 +534,7 @@ public void test_DIV_BY_Zero() { } @Test - public void test_HybridExpression() { + public void test_HybridArithmeticExpression() { String expr = "avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * " + "(avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m])"; IExpression ast = MetricExpressionASTBuilder.parse(expr); @@ -581,4 +582,13 @@ public void test_MetricSelectExpression() { Assertions.assertEquals("jvm-metrics.cpu{appName = \"a\"}[5m]", MetricExpressionASTBuilder.parse(expr).serializeToText()); } } + + @Test + public void test_FunctionExpression() { + String expr = "irate(jvm-metrics.cpu{appName = 'a'} [5m] ) > 1"; + IExpression ast = MetricExpressionASTBuilder.parse(expr); + Assertions.assertInstanceOf(ComparisonExpression.class, ast); + Assertions.assertInstanceOf(FunctionExpression.class, ((BinaryExpression) ast).getLhs()); + Assertions.assertEquals("irate(jvm-metrics.cpu{appName = \"a\"}[5m]) > 1", ast.serializeToText()); + } } From f8531414b5f4639fee7f2860d2edbab1dffd0cb6 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 4 Jun 2025 22:09:46 +0800 Subject: [PATCH 09/22] Remove pushdown optimizer --- .../ast/MetricExpressionOptimizer.java | 87 ------------------- .../pipeline/QueryPipelineBuilder.java | 40 ++++++++- .../QueryPipelineBuilderSettings.java | 31 +++++++ .../pipeline/step/FunctionCallStep.java | 37 +++++++- .../pipeline/step/FunctionStep.java | 54 ------------ .../pipeline/step/MetricQueryStep.java | 2 + .../ast/MetricExpressionOptimizerTest.java | 13 --- .../pipeline/step/ArithmeticStepTest.java | 4 +- 8 files changed, 108 insertions(+), 160 deletions(-) create mode 100644 server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilderSettings.java delete mode 100644 server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionStep.java diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java index 5cb7218f55..b1cba40134 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizer.java @@ -17,12 +17,7 @@ package org.bithon.server.metric.expression.ast; -import org.bithon.component.commons.expression.ArithmeticExpression; -import org.bithon.component.commons.expression.ComparisonExpression; -import org.bithon.component.commons.expression.ConditionalExpression; import org.bithon.component.commons.expression.IExpression; -import org.bithon.component.commons.expression.LiteralExpression; -import org.bithon.component.commons.expression.optimzer.AbstractOptimizer; import org.bithon.component.commons.expression.optimzer.ConstantFoldingOptimizer; /** @@ -53,88 +48,6 @@ public IExpression visit(MetricExpectedExpression expression) { public static IExpression optimize(IExpression expression) { return expression.accept(new ExtendedConstantFoldingOptimizer()) - //.accept(new OperatorPushingOptimizer()) ; } - - /** - * for comparison expression: sum(metric) > 5, - * it's parsed as: - *
-     *     ComparisonExpression
-     *     ├── MetricExpression
-     *     └── ConstantExpression
-     * 
- * We can push down the predicate expression and constant expression to the metric expression as - *
-     *     MetricExpression
-     *     └── ExpectedExpression(ConstantExpression)
-     * 
- *

- * for arithmetic expression: sum(metric) + 5, - * it's parsed as: - *

-     *     ArithmeticExpression
-     *     ├── MetricExpression
-     *     └── ConstantExpression
-     *     
- * We can push down the constant expression to the metric expression as - *
-     *     MetricExpression
-     *     └── PostExpression(ConstantExpression)
-     *  
- */ - public static class OperatorPushingOptimizer extends AbstractOptimizer implements IMetricExpressionVisitor { - public static IExpression optimize(IExpression expression) { - return expression.accept(new OperatorPushingOptimizer()); - } - - private OperatorPushingOptimizer() { - } - - @Override - public IExpression visit(MetricAggregateExpression expression) { - return expression; - } - - @Override - public IExpression visit(ConditionalExpression expression) { - IExpression lhs = expression.getLhs().accept(this); - IExpression rhs = expression.getRhs().accept(this); - - if (lhs instanceof MetricAggregateExpression metricExpression - && rhs instanceof LiteralExpression expectedExpression) { - metricExpression.setExpected(expectedExpression); - if (expression instanceof ComparisonExpression.LT) { - metricExpression.setPredicate(PredicateEnum.LT); - } else if (expression instanceof ComparisonExpression.GT) { - metricExpression.setPredicate(PredicateEnum.GT); - } else if (expression instanceof ComparisonExpression.LTE) { - metricExpression.setPredicate(PredicateEnum.LTE); - } else if (expression instanceof ComparisonExpression.GTE) { - metricExpression.setPredicate(PredicateEnum.GTE); - } else if (expression instanceof ComparisonExpression.EQ) { - metricExpression.setPredicate(PredicateEnum.EQ); - } else if (expression instanceof ComparisonExpression.NE) { - metricExpression.setPredicate(PredicateEnum.NE); - } else { - throw new UnsupportedOperationException("Unsupported comparison expression: " + expression.getClass().getSimpleName()); - } - return metricExpression; - } - - return expression; - } - - /** - * for expression like: sum(metric) + sum(metric2), apply optimization if: - * 1. they're on the same data source with same filters and same group-by, - * 2. the underlying data source supports multiple metrics in a single query - * then we can merge them into a single metric expression - */ - @Override - public IExpression visit(ArithmeticExpression expression) { - return super.visit(expression); - } - } } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java index bc5554c08a..7b3496a463 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java @@ -30,6 +30,7 @@ import org.bithon.server.metric.expression.ast.MetricExpectedExpression; import org.bithon.server.metric.expression.ast.MetricExpressionASTBuilder; import org.bithon.server.metric.expression.ast.MetricExpressionOptimizer; +import org.bithon.server.metric.expression.ast.PredicateEnum; import org.bithon.server.metric.expression.pipeline.step.ArithmeticStep; import org.bithon.server.metric.expression.pipeline.step.FilterStep; import org.bithon.server.metric.expression.pipeline.step.LiteralQueryStep; @@ -53,6 +54,7 @@ public class QueryPipelineBuilder { private IDataSourceApi dataSourceApi; private IntervalRequest intervalRequest; private String condition; + private QueryPipelineBuilderSettings settings = QueryPipelineBuilderSettings.DEFAULT; public static QueryPipelineBuilder builder() { return new QueryPipelineBuilder(); @@ -73,6 +75,11 @@ public QueryPipelineBuilder condition(String condition) { return this; } + public QueryPipelineBuilder settings(QueryPipelineBuilderSettings settings) { + this.settings = settings; + return this; + } + public IQueryStep build(String expression) { IExpression expr = MetricExpressionASTBuilder.parse(expression); @@ -193,12 +200,43 @@ public IQueryStep visit(ArithmeticExpression expression) { case "-" -> new ArithmeticStep.Sub(expression.getLhs().accept(this), expression.getRhs().accept(this)); case "*" -> new ArithmeticStep.Mul(expression.getLhs().accept(this), expression.getRhs().accept(this)); case "/" -> new ArithmeticStep.Div(expression.getLhs().accept(this), expression.getRhs().accept(this)); - default -> throw new UnsupportedOperationException("Unsupported arithmetic expression: " + expression.getType()); + default -> + throw new UnsupportedOperationException("Unsupported arithmetic expression: " + expression.getType()); }; } + /** + * Push down the filter condition to the source step. + */ @Override public IQueryStep visit(ConditionalExpression expression) { + if (settings.isPushdownPostFilter()) { + IExpression lhs = expression.getLhs(); + IExpression rhs = expression.getRhs(); + if (lhs instanceof MetricAggregateExpression metricExpression + && rhs instanceof MetricExpectedExpression expectedExpression) { + if (expression instanceof ComparisonExpression.LT) { + metricExpression.setPredicate(PredicateEnum.LT); + } else if (expression instanceof ComparisonExpression.GT) { + metricExpression.setPredicate(PredicateEnum.GT); + } else if (expression instanceof ComparisonExpression.LTE) { + metricExpression.setPredicate(PredicateEnum.LTE); + } else if (expression instanceof ComparisonExpression.GTE) { + metricExpression.setPredicate(PredicateEnum.GTE); + } else if (expression instanceof ComparisonExpression.EQ) { + metricExpression.setPredicate(PredicateEnum.EQ); + } else if (expression instanceof ComparisonExpression.NE) { + metricExpression.setPredicate(PredicateEnum.NE); + } else { + throw new UnsupportedOperationException("Unsupported comparison expression: " + expression.getClass().getSimpleName()); + } + + metricExpression.setExpected(expectedExpression.getExpected()); + metricExpression.setOffset(expectedExpression.getOffset()); + return this.visit(metricExpression); + } + } + IQueryStep source = expression.getLhs().accept(this); if (expression instanceof ComparisonExpression.LT) { diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilderSettings.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilderSettings.java new file mode 100644 index 0000000000..46d59fa83f --- /dev/null +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilderSettings.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.metric.expression.pipeline; + +import lombok.Data; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/4 21:47 + */ +@Data +public class QueryPipelineBuilderSettings { + public static final QueryPipelineBuilderSettings DEFAULT = new QueryPipelineBuilderSettings(); + + private boolean pushdownPostFilter = false; + private boolean pushdownArithmetic = false; +} diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java index 144d6c5f04..aed99973f2 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java @@ -16,11 +16,42 @@ package org.bithon.server.metric.expression.pipeline.step; + +import org.bithon.server.datasource.query.pipeline.IQueryStep; +import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + /** - * Plan for the {@link org.bithon.component.commons.expression.FunctionExpression} + * Map to the {@link org.bithon.component.commons.expression.FunctionExpression} * * @author frank.chen021@outlook.com - * @date 2025/6/4 21:28 + * @date 2/6/25 9:29 pm */ -public class FunctionCallStep { +public abstract class FunctionCallStep implements IQueryStep { + private final IQueryStep source; + + public FunctionCallStep(IQueryStep source) { + this.source = source; + } + + @Override + public boolean isScalar() { + return source.isScalar(); + } + + public static FunctionCallStep apply(IQueryStep source, + Function function) { + return new FunctionCallStep(source) { + @Override + public CompletableFuture execute() throws Exception { + return source.execute().thenApply(function); + } + }; + } + + public static Function SUM = (result) -> { + return result; + }; } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionStep.java deleted file mode 100644 index 4404ac0472..0000000000 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionStep.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020 bithon.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bithon.server.metric.expression.pipeline.step; - - -import org.bithon.server.datasource.query.pipeline.IQueryStep; -import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; - -import java.util.concurrent.CompletableFuture; -import java.util.function.Function; - -/** - * @author frank.chen021@outlook.com - * @date 2/6/25 9:29 pm - */ -public abstract class FunctionStep implements IQueryStep { - private final IQueryStep source; - - public FunctionStep(IQueryStep source) { - this.source = source; - } - - @Override - public boolean isScalar() { - return source.isScalar(); - } - - public static FunctionStep apply(IQueryStep source, Function function) { - return new FunctionStep(source) { - @Override - public CompletableFuture execute() throws Exception { - return source.execute().thenApply(function); - } - }; - } - - public static Function SUM = (result) -> { - return result; - }; -} diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java index e3515a17c6..ef13b71158 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java @@ -18,12 +18,14 @@ import org.bithon.component.commons.utils.CollectionUtils; +import org.bithon.component.commons.utils.HumanReadableDuration; import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.TimestampSpec; import org.bithon.server.datasource.query.pipeline.ColumnarTable; import org.bithon.server.datasource.query.pipeline.IQueryStep; import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; import org.bithon.server.web.service.datasource.api.IDataSourceApi; +import org.bithon.server.web.service.datasource.api.IntervalRequest; import org.bithon.server.web.service.datasource.api.QueryField; import org.bithon.server.web.service.datasource.api.QueryRequest; diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java index 17f913014b..6ce08b027b 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/ast/MetricExpressionOptimizerTest.java @@ -17,7 +17,6 @@ package org.bithon.server.metric.expression.ast; -import org.bithon.component.commons.expression.ComparisonExpression; import org.bithon.component.commons.expression.IExpression; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -94,16 +93,4 @@ public void test_Optimize_ConstantFolding_Duration() { ast = MetricExpressionOptimizer.optimize(ast); Assertions.assertEquals("sum(dataSource.metric) + 86460", ast.serializeToText()); } - - @Test - public void test_Optimize_PushDownFilterExpression() { - String expression = "sum(dataSource.metric) > 5"; - IExpression ast = MetricExpressionASTBuilder.parse(expression); - Assertions.assertInstanceOf(ComparisonExpression.class, ast); - Assertions.assertEquals("sum(dataSource.metric) > 5", ast.serializeToText()); - - ast = MetricExpressionOptimizer.OperatorPushingOptimizer.optimize(ast); - Assertions.assertInstanceOf(MetricAggregateExpression.class, ast); - Assertions.assertEquals("sum(dataSource.metric) > 5", ast.serializeToText()); - } } diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java index 89366f86e6..63c7226a9d 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java @@ -2731,7 +2731,7 @@ public void test_FilterStep_Double_GT() throws Exception { } // - // Case 2, > 3, tow rows satisfy the filter condition + // Case 2, > 3, two rows satisfy the filter condition // { IQueryStep evaluator = QueryPipelineBuilder.builder() @@ -2886,7 +2886,7 @@ public void test_FilterStep_Double_GTE() throws Exception { } // - // Case 4, >= 5, 1 rows satisfies the filter condition + // Case 4, >= 5, some rows satisfy the filter condition // { IQueryStep evaluator = QueryPipelineBuilder.builder() From b462407466801febc94e88196398b7b681f8955b Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 4 Jun 2025 22:17:17 +0800 Subject: [PATCH 10/22] Don't accept QueryRequest as ctor for MetricQueryStep --- .../pipeline/QueryPipelineBuilder.java | 71 ++++++------ .../pipeline/step/MetricQueryStep.java | 105 +++++++++++++++--- 2 files changed, 123 insertions(+), 53 deletions(-) diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java index 7b3496a463..440d5879ca 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java @@ -17,6 +17,11 @@ package org.bithon.server.metric.expression.pipeline; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.bithon.component.commons.expression.ArithmeticExpression; import org.bithon.component.commons.expression.ComparisonExpression; import org.bithon.component.commons.expression.ConditionalExpression; @@ -38,12 +43,6 @@ import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; import org.bithon.server.web.service.datasource.api.QueryField; -import org.bithon.server.web.service.datasource.api.QueryRequest; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * @author frank.chen021@outlook.com @@ -105,29 +104,29 @@ public IQueryStep visit(MetricAggregateExpression expression) { // Create expression as: (current - base) / base QueryField metricField = expression.getMetric(); String expr = StringUtils.format("%s(%s) * 1.0", metricField.getAggregator(), metricField.getField()); - MetricQueryStep curr = new MetricQueryStep(QueryRequest.builder() - .dataSource(expression.getFrom()) - .filterExpression(filterExpression) - .groupBy(expression.getGroupBy()) - .fields(List.of(new QueryField(metricField.getName(), metricField.getField(), null, expr))) - .interval(intervalRequest) - .build(), - dataSourceApi); + MetricQueryStep curr = MetricQueryStep.builder() + .dataSource(expression.getFrom()) + .filterExpression(filterExpression) + .groupBy(expression.getGroupBy()) + .fields(List.of(new QueryField(metricField.getName(), metricField.getField(), null, expr))) + .interval(intervalRequest) + .dataSourceApi(dataSourceApi) + .build(); - MetricQueryStep base = new MetricQueryStep(QueryRequest.builder() - .dataSource(expression.getFrom()) - .filterExpression(filterExpression) - .groupBy(expression.getGroupBy()) - .fields(List.of(new QueryField( - // Use offset AS the output name - expression.getOffset().toString(), - metricField.getField(), - null, - expr))) - .interval(intervalRequest) - .offset(expression.getOffset()) - .build(), - dataSourceApi); + MetricQueryStep base = MetricQueryStep.builder() + .dataSource(expression.getFrom()) + .filterExpression(filterExpression) + .groupBy(expression.getGroupBy()) + .fields(List.of(new QueryField( + // Use offset AS the output name + expression.getOffset().toString(), + metricField.getField(), + null, + expr))) + .interval(intervalRequest) + .offset(expression.getOffset()) + .dataSourceApi(dataSourceApi) + .build(); // return new ArithmeticStep.Div( @@ -146,14 +145,14 @@ public IQueryStep visit(MetricAggregateExpression expression) { metricField.getName(), expression.getOffset().toString() ); } else { - return new MetricQueryStep(QueryRequest.builder() - .dataSource(expression.getFrom()) - .filterExpression(filterExpression) - .groupBy(expression.getGroupBy()) - .fields(List.of(expression.getMetric())) - .interval(intervalRequest) - .build(), - dataSourceApi); + return MetricQueryStep.builder() + .dataSource(expression.getFrom()) + .filterExpression(filterExpression) + .groupBy(expression.getGroupBy()) + .fields(List.of(expression.getMetric())) + .interval(intervalRequest) + .dataSourceApi(dataSourceApi) + .build(); } } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java index ef13b71158..84948ecef9 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletableFuture; /** @@ -40,35 +41,97 @@ * @date 4/4/25 3:48 pm */ public class MetricQueryStep implements IQueryStep { - private final QueryRequest queryRequest; + private final String dataSource; + private final IntervalRequest interval; + private final String filterExpression; + private final List fields; + private final Set groupBy; + private final HumanReadableDuration offset; private final IDataSourceApi dataSourceApi; private final boolean isScalar; // Make sure the evaluation is executed ONLY ONCE when the expression is referenced multiple times private volatile CompletableFuture cachedResponse; - public MetricQueryStep(QueryRequest queryRequest, - IDataSourceApi dataSourceApi) { - this.queryRequest = queryRequest; - this.dataSourceApi = dataSourceApi; - this.isScalar = computeIsScalar(queryRequest); + private MetricQueryStep(Builder builder) { + this.dataSource = builder.dataSource; + this.interval = builder.interval; + this.filterExpression = builder.filterExpression; + this.fields = builder.fields; + this.groupBy = builder.groupBy; + this.offset = builder.offset; + this.dataSourceApi = builder.dataSourceApi; + this.isScalar = computeIsScalar(); } - private boolean computeIsScalar(QueryRequest queryRequest) { - if (CollectionUtils.isNotEmpty(queryRequest.getGroupBy())) { + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String dataSource; + private IntervalRequest interval; + private String filterExpression; + private List fields; + private Set groupBy; + private HumanReadableDuration offset; + private IDataSourceApi dataSourceApi; + + public Builder dataSource(String dataSource) { + this.dataSource = dataSource; + return this; + } + + public Builder interval(IntervalRequest interval) { + this.interval = interval; + return this; + } + + public Builder filterExpression(String filterExpression) { + this.filterExpression = filterExpression; + return this; + } + + public Builder fields(List fields) { + this.fields = fields; + return this; + } + + public Builder groupBy(Set groupBy) { + this.groupBy = groupBy; + return this; + } + + public Builder offset(HumanReadableDuration offset) { + this.offset = offset; + return this; + } + + public Builder dataSourceApi(IDataSourceApi dataSourceApi) { + this.dataSourceApi = dataSourceApi; + return this; + } + + public MetricQueryStep build() { + return new MetricQueryStep(this); + } + } + + private boolean computeIsScalar() { + if (CollectionUtils.isNotEmpty(groupBy)) { // If GROUP-BY is not empty, obviously it is not a scalar because the result set contains multiple rows for different groups return false; } - if (queryRequest.getInterval().getBucketCount() != null && queryRequest.getInterval().getBucketCount() == 1) { + if (interval.getBucketCount() != null && interval.getBucketCount() == 1) { // ONLY one bucket is requested, the result set is a scalar return true; } - TimeSpan start = queryRequest.getInterval().getStartISO8601(); - TimeSpan end = queryRequest.getInterval().getEndISO8601(); + TimeSpan start = interval.getStartISO8601(); + TimeSpan end = interval.getEndISO8601(); long intervalLength = (end.getMilliseconds() - start.getMilliseconds()) / 1000; - return queryRequest.getInterval().getStep() != null && queryRequest.getInterval().getStep() == intervalLength; + return interval.getStep() != null && interval.getStep() == intervalLength; } @Override @@ -83,16 +146,24 @@ public CompletableFuture execute() { if (cachedResponse == null) { cachedResponse = CompletableFuture.supplyAsync(() -> { try { + QueryRequest queryRequest = QueryRequest.builder() + .dataSource(dataSource) + .interval(interval) + .filterExpression(filterExpression) + .fields(fields) + .groupBy(groupBy) + .offset(offset) + .build(); + ColumnarTable columnTable = dataSourceApi.timeseriesV5(queryRequest); List keys = new ArrayList<>(); keys.add(TimestampSpec.COLUMN_ALIAS); - keys.addAll(queryRequest.getGroupBy() != null ? queryRequest.getGroupBy() : Collections.emptySet()); + keys.addAll(groupBy != null ? groupBy : Collections.emptySet()); - List valNames = queryRequest.getFields() - .stream() - .map(QueryField::getName) - .toList(); + List valNames = fields.stream() + .map(QueryField::getName) + .toList(); return PipelineQueryResult.builder() .rows(columnTable.rowCount()) From 975f1f0fa1a3142428d705107aab9fbeb9cef434 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 4 Jun 2025 23:57:23 +0800 Subject: [PATCH 11/22] Add logical plan/physical plan --- .../datasource/query/IDataSourceReader.java | 8 +- .../query/plan/logical/BinaryOp.java | 26 ++++ .../query/plan/logical/ILogicalPlan.java | 27 +++++ .../query/plan/logical/LogicalAggregate.java | 31 +++++ .../query/plan/logical/LogicalBinaryOp.java | 27 +++++ .../query/plan/logical/LogicalFilter.java | 29 +++++ .../query/plan/logical/LogicalPlanner.java | 32 +++++ .../query/plan/logical/LogicalScalar.java | 23 ++++ .../query/plan/logical/LogicalTableScan.java | 29 +++++ .../query/plan/logical/PredicateEnum.java | 113 ++++++++++++++++++ .../{pipeline => plan/physical}/Column.java | 4 +- .../physical}/ColumnarTable.java | 2 +- .../physical}/CompositeKey.java | 2 +- .../physical}/DoubleColumn.java | 2 +- .../query/plan/physical/IPhysicalPlan.java | 27 +++++ .../physical}/IQueryStep.java | 2 +- .../physical}/LongColumn.java | 4 +- .../physical}/PipelineQueryResult.java | 2 +- .../physical}/StringColumn.java | 4 +- .../query/pipeline/DoubleColumnTest.java | 2 + .../query/pipeline/LongColumnTest.java | 2 + .../query/pipeline/StringColumnTest.java | 2 + .../reader/jdbc/JdbcDataSourceReader.java | 11 +- .../jdbc/pipeline/JdbcPipelineBuilder.java | 2 +- .../reader/jdbc/pipeline/JdbcReadStep.java | 8 +- .../SlidingWindowAggregationStep.java | 8 +- .../pipeline/SlidingWindowAggregator.java | 6 +- .../jdbc/SlidingWindowAggregatorTest.java | 4 +- .../reader/vm/VMDataSourceReader.java | 4 +- .../metric/expression/api/MetricQueryApi.java | 6 +- .../pipeline/QueryPipelineBuilder.java | 2 +- .../pipeline/step/ArithmeticStep.java | 8 +- .../pipeline/step/ColumnOperator.java | 6 +- .../expression/pipeline/step/FilterStep.java | 8 +- .../pipeline/step/FunctionCallStep.java | 4 +- .../expression/pipeline/step/HashJoiner.java | 6 +- .../pipeline/step/LiteralQueryStep.java | 12 +- .../pipeline/step/MetricQueryStep.java | 6 +- .../expression/plan/LogicalPlanBuilder.java | 51 ++++++++ .../pipeline/step/ArithmeticStepTest.java | 14 +-- .../pipeline/step/LiteralQueryStepTest.java | 2 +- .../jdbc/tracing/reader/TraceJdbcReader.java | 4 +- .../server/storage/tracing/ITraceReader.java | 2 +- .../datasource/api/DataSourceService.java | 2 +- .../datasource/api/IDataSourceApi.java | 2 +- .../datasource/api/impl/DataSourceApi.java | 2 +- 46 files changed, 507 insertions(+), 73 deletions(-) create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/BinaryOp.java create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlan.java create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalBinaryOp.java create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalFilter.java create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanner.java create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/PredicateEnum.java rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{pipeline => plan/physical}/Column.java (94%) rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{pipeline => plan/physical}/ColumnarTable.java (98%) rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{pipeline => plan/physical}/CompositeKey.java (96%) rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{pipeline => plan/physical}/DoubleColumn.java (98%) create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{pipeline => plan/physical}/IQueryStep.java (93%) rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{pipeline => plan/physical}/LongColumn.java (95%) rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{pipeline => plan/physical}/PipelineQueryResult.java (94%) rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{pipeline => plan/physical}/StringColumn.java (95%) create mode 100644 server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java index 55e09ca1fb..747da9794b 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java @@ -17,7 +17,9 @@ package org.bithon.server.datasource.query; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import java.util.List; @@ -28,6 +30,10 @@ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public interface IDataSourceReader extends AutoCloseable { + default IPhysicalPlan plan(ILogicalPlan plan) { + throw new UnsupportedOperationException("This data source does not support logical plan execution"); + } + ColumnarTable timeseries(Query query); /** diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/BinaryOp.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/BinaryOp.java new file mode 100644 index 0000000000..57cc8ab30d --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/BinaryOp.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:33 + */ +public enum BinaryOp { + ADD, SUB, MUL, DIV +} + diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlan.java new file mode 100644 index 0000000000..b9594a712c --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlan.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:31 + */ +public sealed interface ILogicalPlan + permits LogicalTableScan, LogicalAggregate, LogicalBinaryOp, + LogicalScalar, LogicalFilter { +} + diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java new file mode 100644 index 0000000000..7b4902c706 --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +import java.util.List; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:32 + */ +public record LogicalAggregate( + String func, // e.g., "sum", "avg" + ILogicalPlan input, // e.g., table scan + List groupBy // e.g., ["appName"] +) implements ILogicalPlan { +} + diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalBinaryOp.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalBinaryOp.java new file mode 100644 index 0000000000..16a72da2c7 --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalBinaryOp.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:32 + */ +public record LogicalBinaryOp( + ILogicalPlan left, + BinaryOp op, + ILogicalPlan right +) implements ILogicalPlan {} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalFilter.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalFilter.java new file mode 100644 index 0000000000..77272cdbfe --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalFilter.java @@ -0,0 +1,29 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +import org.bithon.server.metric.expression.ast.PredicateEnum; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:34 + */ +public record LogicalFilter( + ILogicalPlan left, + PredicateEnum op, + ILogicalPlan right +) implements ILogicalPlan {} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanner.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanner.java new file mode 100644 index 0000000000..b964f0fb71 --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanner.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +import org.bithon.component.commons.expression.IExpression; +import org.bithon.server.metric.expression.plan.ILogicalPlan; +import org.bithon.server.metric.expression.plan.LogicalPlanBuilder; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:36 + */ +public class LogicalPlanner { + public static ILogicalPlan plan(IExpression expression) { + return expression.accept(new LogicalPlanBuilder()); + } + +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java new file mode 100644 index 0000000000..8837ee419c --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java @@ -0,0 +1,23 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:33 + */ +public record LogicalScalar(double value) implements ILogicalPlan {} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java new file mode 100644 index 0000000000..9991f5791d --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java @@ -0,0 +1,29 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +import org.bithon.component.commons.expression.IExpression; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:31 + */ +public record LogicalTableScan( + String table, + IExpression labelMatchers // = filters like {job="abc"} +) implements ILogicalPlan { +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/PredicateEnum.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/PredicateEnum.java new file mode 100644 index 0000000000..8bc30e60e3 --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/PredicateEnum.java @@ -0,0 +1,113 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +import org.bithon.component.commons.expression.ComparisonExpression; +import org.bithon.component.commons.expression.ConditionalExpression; +import org.bithon.component.commons.expression.IExpression; + +/** + * @author frank.chen021@outlook.com + * @date 2024/7/20 17:12 + */ +public enum PredicateEnum { + + LT { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.LT(left, right); + } + + @Override + public String toString() { + return "<"; + } + }, + + LTE { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.LTE(left, right); + } + + @Override + public String toString() { + return "<="; + } + }, + + GT { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.GT(left, right); + } + + @Override + public String toString() { + return ">"; + } + }, + + GTE { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.GTE(left, right); + } + + + @Override + public String toString() { + return ">="; + } + }, + + NE { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.NE(left, right); + } + + @Override + public String toString() { + return "<>"; + } + }, + EQ { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ComparisonExpression.EQ(left, right); + } + + @Override + public String toString() { + return "="; + } + }, + IS_NULL { + @Override + public IExpression createComparisonExpression(IExpression left, IExpression right) { + return new ConditionalExpression.IsNull(left); + } + + @Override + public String toString() { + return "IS NULL"; + } + }; + + public abstract IExpression createComparisonExpression(IExpression left, IExpression right); +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/Column.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/Column.java similarity index 94% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/Column.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/Column.java index 8ecf9402cc..5fb13aa2f6 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/Column.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/Column.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.pipeline; +package org.bithon.server.datasource.query.plan.physical; import org.bithon.component.commons.expression.IDataType; @@ -79,7 +79,7 @@ static Column create(String name, String type, int initCapacity) { return new DoubleColumn(name, initCapacity); } if (type.equals(IDataType.DATETIME_MILLI.name())) { - // DATETIME_MILLI is a long + // DATETIME_MILLI is type of LONG at storage layer return new LongColumn(name, initCapacity); } throw new IllegalArgumentException("Unsupported column type: " + type); diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/ColumnarTable.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ColumnarTable.java similarity index 98% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/ColumnarTable.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ColumnarTable.java index 580344a7ec..aea81c48ec 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/ColumnarTable.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ColumnarTable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.pipeline; +package org.bithon.server.datasource.query.plan.physical; import java.util.ArrayList; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/CompositeKey.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/CompositeKey.java similarity index 96% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/CompositeKey.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/CompositeKey.java index 48670595fe..0abd2ec9af 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/CompositeKey.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/CompositeKey.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.pipeline; +package org.bithon.server.datasource.query.plan.physical; import java.util.Arrays; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/DoubleColumn.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/DoubleColumn.java similarity index 98% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/DoubleColumn.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/DoubleColumn.java index d0e031b68d..38fa1c1632 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/DoubleColumn.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/DoubleColumn.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.pipeline; +package org.bithon.server.datasource.query.plan.physical; import org.bithon.component.commons.expression.IDataType; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java new file mode 100644 index 0000000000..c5811cd2f7 --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.physical; + +import java.util.concurrent.CompletableFuture; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:49 + */ +public interface IPhysicalPlan { + CompletableFuture execute() throws Exception; +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/IQueryStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IQueryStep.java similarity index 93% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/IQueryStep.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IQueryStep.java index fe3591c1ed..747b718ac6 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/IQueryStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IQueryStep.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.pipeline; +package org.bithon.server.datasource.query.plan.physical; import java.util.concurrent.CompletableFuture; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/LongColumn.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LongColumn.java similarity index 95% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/LongColumn.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LongColumn.java index 26dfa6872e..2874acc1d9 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/LongColumn.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LongColumn.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.pipeline; +package org.bithon.server.datasource.query.plan.physical; import org.bithon.component.commons.expression.IDataType; @@ -113,7 +113,7 @@ public int size() { @Override public Column filter(BitSet keep) { - org.bithon.server.datasource.query.pipeline.LongColumn filtered = new org.bithon.server.datasource.query.pipeline.LongColumn(this.name, keep.cardinality()); + LongColumn filtered = new LongColumn(this.name, keep.cardinality()); for (int i = 0; i < this.size; i++) { if (keep.get(i)) { filtered.addLong(this.data[i]); diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/PipelineQueryResult.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PipelineQueryResult.java similarity index 94% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/PipelineQueryResult.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PipelineQueryResult.java index 9e6892b03d..a1cae6ca5e 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/PipelineQueryResult.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PipelineQueryResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.pipeline; +package org.bithon.server.datasource.query.plan.physical; import lombok.AllArgsConstructor; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/StringColumn.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/StringColumn.java similarity index 95% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/StringColumn.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/StringColumn.java index d761d2b230..39ceff6378 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/pipeline/StringColumn.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/StringColumn.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.pipeline; +package org.bithon.server.datasource.query.plan.physical; import org.bithon.component.commons.expression.IDataType; @@ -125,7 +125,7 @@ public int size() { @Override public Column filter(BitSet keep) { - org.bithon.server.datasource.query.pipeline.StringColumn filtered = new org.bithon.server.datasource.query.pipeline.StringColumn(this.name, keep.cardinality()); + StringColumn filtered = new StringColumn(this.name, keep.cardinality()); for (int i = 0; i < this.size; i++) { if (keep.get(i)) { filtered.addString(this.data[i]); diff --git a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/DoubleColumnTest.java b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/DoubleColumnTest.java index e919f141f6..c83927e158 100644 --- a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/DoubleColumnTest.java +++ b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/DoubleColumnTest.java @@ -16,6 +16,8 @@ package org.bithon.server.datasource.query.pipeline; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.DoubleColumn; import org.junit.jupiter.api.Test; import java.util.BitSet; diff --git a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/LongColumnTest.java b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/LongColumnTest.java index a8ab59c314..1ef925b7db 100644 --- a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/LongColumnTest.java +++ b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/LongColumnTest.java @@ -16,6 +16,8 @@ package org.bithon.server.datasource.query.pipeline; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.LongColumn; import org.junit.jupiter.api.Test; import java.util.BitSet; diff --git a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/StringColumnTest.java b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/StringColumnTest.java index 0f9eb9551c..0377e81bc8 100644 --- a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/StringColumnTest.java +++ b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/StringColumnTest.java @@ -16,6 +16,8 @@ package org.bithon.server.datasource.query.pipeline; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.StringColumn; import org.junit.jupiter.api.Test; import java.util.BitSet; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java index f5745ad1f1..1afe1554b7 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java @@ -31,8 +31,10 @@ import org.bithon.server.datasource.query.OrderBy; import org.bithon.server.datasource.query.Query; import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; -import org.bithon.server.datasource.query.pipeline.IQueryStep; +import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; import org.bithon.server.datasource.query.setting.QuerySettings; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.pipeline.JdbcPipelineBuilder; @@ -114,6 +116,11 @@ public JdbcDataSourceReader(DSLContext dslContext, ISqlDialect sqlDialect, Query this.shouldCloseContext = false; } + @Override + public IPhysicalPlan plan(ILogicalPlan plan) { + return IDataSourceReader.super.plan(plan); + } + @Override public ColumnarTable timeseries(Query query) { SelectStatementBuilder statementBuilder = SelectStatementBuilder.builder() diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java index 1a6c8ddb20..d4fab2389b 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java @@ -24,7 +24,7 @@ import org.bithon.server.datasource.query.Order; import org.bithon.server.datasource.query.ast.ExpressionNode; import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.pipeline.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.statement.ast.OrderByClause; import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java index d7639624e5..b6d70a983c 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java @@ -19,10 +19,10 @@ import lombok.extern.slf4j.Slf4j; import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; -import org.bithon.server.datasource.query.pipeline.IQueryStep; -import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; import org.jooq.Cursor; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java index dea7be84b0..1d4940d18a 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java @@ -20,10 +20,10 @@ import lombok.extern.slf4j.Slf4j; import org.bithon.server.datasource.TimestampSpec; import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; -import org.bithon.server.datasource.query.pipeline.IQueryStep; -import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import java.time.Duration; import java.util.List; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregator.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregator.java index e2e127eae0..6dc8ffe945 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregator.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregator.java @@ -16,9 +16,9 @@ package org.bithon.server.datasource.reader.jdbc.pipeline; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; -import org.bithon.server.datasource.query.pipeline.CompositeKey; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.CompositeKey; import java.time.Duration; import java.util.ArrayList; diff --git a/server/datasource/reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc/SlidingWindowAggregatorTest.java b/server/datasource/reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc/SlidingWindowAggregatorTest.java index 78f7d097a8..2b08f4d9fc 100644 --- a/server/datasource/reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc/SlidingWindowAggregatorTest.java +++ b/server/datasource/reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc/SlidingWindowAggregatorTest.java @@ -17,8 +17,8 @@ package org.bithon.server.datasource.reader.jdbc; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.datasource.reader.jdbc.pipeline.SlidingWindowAggregator; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/server/datasource/reader-vm/src/main/java/org/bithon/server/datasource/reader/vm/VMDataSourceReader.java b/server/datasource/reader-vm/src/main/java/org/bithon/server/datasource/reader/vm/VMDataSourceReader.java index b3b3f1a16a..5416b1e884 100644 --- a/server/datasource/reader-vm/src/main/java/org/bithon/server/datasource/reader/vm/VMDataSourceReader.java +++ b/server/datasource/reader-vm/src/main/java/org/bithon/server/datasource/reader/vm/VMDataSourceReader.java @@ -32,8 +32,8 @@ import org.bithon.server.datasource.query.Query; import org.bithon.server.datasource.query.ast.ExpressionNode; import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java index fe4da6273a..7466340c38 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java @@ -25,9 +25,9 @@ import lombok.NoArgsConstructor; import org.bithon.component.commons.Experimental; import org.bithon.server.commons.time.TimeSpan; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.IQueryStep; -import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import org.bithon.server.metric.expression.pipeline.QueryPipelineBuilder; import org.bithon.server.web.service.WebServiceModuleEnabler; import org.bithon.server.web.service.datasource.api.IDataSourceApi; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java index 440d5879ca..a0daa59045 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java @@ -29,7 +29,7 @@ import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.utils.StringUtils; import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.pipeline.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; import org.bithon.server.metric.expression.ast.IMetricExpressionVisitor; import org.bithon.server.metric.expression.ast.MetricAggregateExpression; import org.bithon.server.metric.expression.ast.MetricExpectedExpression; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java index cf2b099872..e5068377e9 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java @@ -17,10 +17,10 @@ package org.bithon.server.metric.expression.pipeline.step; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; -import org.bithon.server.datasource.query.pipeline.IQueryStep; -import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import java.util.ArrayList; import java.util.Arrays; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ColumnOperator.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ColumnOperator.java index a692071123..b53de4788d 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ColumnOperator.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ColumnOperator.java @@ -19,9 +19,9 @@ import org.bithon.component.commons.expression.IDataTypeIndex; import org.bithon.component.commons.utils.StringUtils; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.DoubleColumn; -import org.bithon.server.datasource.query.pipeline.LongColumn; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.DoubleColumn; +import org.bithon.server.datasource.query.plan.physical.LongColumn; /** * @author frank.chen021@outlook.com diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java index 8b671dd61f..f17b792e30 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java @@ -18,10 +18,10 @@ import org.bithon.component.commons.expression.IDataType; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; -import org.bithon.server.datasource.query.pipeline.IQueryStep; -import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import org.bithon.server.metric.expression.ast.MetricExpectedExpression; import java.util.concurrent.CompletableFuture; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java index aed99973f2..56789d9323 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java @@ -17,8 +17,8 @@ package org.bithon.server.metric.expression.pipeline.step; -import org.bithon.server.datasource.query.pipeline.IQueryStep; -import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import java.util.concurrent.CompletableFuture; import java.util.function.Function; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/HashJoiner.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/HashJoiner.java index 59f150487a..3d2e736168 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/HashJoiner.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/HashJoiner.java @@ -17,9 +17,9 @@ package org.bithon.server.metric.expression.pipeline.step; import org.bithon.component.commons.utils.CollectionUtils; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; -import org.bithon.server.datasource.query.pipeline.CompositeKey; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.CompositeKey; import java.util.ArrayList; import java.util.HashMap; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStep.java index 277193693f..c4c019d56d 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStep.java @@ -21,12 +21,12 @@ import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; -import org.bithon.server.datasource.query.pipeline.DoubleColumn; -import org.bithon.server.datasource.query.pipeline.IQueryStep; -import org.bithon.server.datasource.query.pipeline.LongColumn; -import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.DoubleColumn; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.LongColumn; +import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import java.util.Collections; import java.util.List; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java index 84948ecef9..4829c43579 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java @@ -21,9 +21,9 @@ import org.bithon.component.commons.utils.HumanReadableDuration; import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.TimestampSpec; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; -import org.bithon.server.datasource.query.pipeline.IQueryStep; -import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; import org.bithon.server.web.service.datasource.api.QueryField; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java new file mode 100644 index 0000000000..9db3b8c7d8 --- /dev/null +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.metric.expression.plan; + +import org.bithon.component.commons.utils.CollectionUtils; +import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; +import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; +import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; +import org.bithon.server.metric.expression.ast.IMetricExpressionVisitor; +import org.bithon.server.metric.expression.ast.MetricAggregateExpression; +import org.bithon.server.metric.expression.ast.MetricSelectExpression; + +import java.util.ArrayList; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:43 + */ +class LogicalPlanBuilder implements IMetricExpressionVisitor { + @Override + public ILogicalPlan visit(MetricSelectExpression expression) { + return new LogicalTableScan( + expression.getFrom(), + expression.getLabelSelectorExpression() + ); + } + + @Override + public ILogicalPlan visit(MetricAggregateExpression expression) { + return new LogicalAggregate( + expression.getMetric().getAggregator(), + new LogicalTableScan(expression.getFrom(), + expression.getLabelSelectorExpression()), + CollectionUtils.isEmpty(expression.getGroupBy()) ? new ArrayList<>() : new ArrayList<>(expression.getGroupBy()) + ); + } +} diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java index 63c7226a9d..2c316d54fe 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java @@ -20,13 +20,13 @@ import org.bithon.component.commons.utils.HumanReadableDuration; import org.bithon.component.commons.utils.HumanReadableNumber; import org.bithon.server.commons.time.TimeSpan; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; -import org.bithon.server.datasource.query.pipeline.DoubleColumn; -import org.bithon.server.datasource.query.pipeline.IQueryStep; -import org.bithon.server.datasource.query.pipeline.LongColumn; -import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; -import org.bithon.server.datasource.query.pipeline.StringColumn; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.DoubleColumn; +import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.LongColumn; +import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; +import org.bithon.server.datasource.query.plan.physical.StringColumn; import org.bithon.server.metric.expression.pipeline.QueryPipelineBuilder; import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStepTest.java index 287cc35c44..38c6b1301a 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStepTest.java @@ -21,7 +21,7 @@ import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.pipeline.PipelineQueryResult; +import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/server/storage-jdbc/src/main/java/org/bithon/server/storage/jdbc/tracing/reader/TraceJdbcReader.java b/server/storage-jdbc/src/main/java/org/bithon/server/storage/jdbc/tracing/reader/TraceJdbcReader.java index 412a3694dd..154faa3b7d 100644 --- a/server/storage-jdbc/src/main/java/org/bithon/server/storage/jdbc/tracing/reader/TraceJdbcReader.java +++ b/server/storage-jdbc/src/main/java/org/bithon/server/storage/jdbc/tracing/reader/TraceJdbcReader.java @@ -39,8 +39,8 @@ import org.bithon.server.datasource.query.Order; import org.bithon.server.datasource.query.OrderBy; import org.bithon.server.datasource.query.Query; -import org.bithon.server.datasource.query.pipeline.Column; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.Column; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.datasource.query.setting.QuerySettings; import org.bithon.server.datasource.reader.jdbc.JdbcDataSourceReader; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; diff --git a/server/storage/src/main/java/org/bithon/server/storage/tracing/ITraceReader.java b/server/storage/src/main/java/org/bithon/server/storage/tracing/ITraceReader.java index 25c9e5ffeb..06d333886e 100644 --- a/server/storage/src/main/java/org/bithon/server/storage/tracing/ITraceReader.java +++ b/server/storage/src/main/java/org/bithon/server/storage/tracing/ITraceReader.java @@ -22,7 +22,7 @@ import org.bithon.server.datasource.query.IDataSourceReader; import org.bithon.server.datasource.query.Limit; import org.bithon.server.datasource.query.OrderBy; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.storage.tracing.mapping.TraceIdMapping; import java.sql.Timestamp; diff --git a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/DataSourceService.java b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/DataSourceService.java index 51868c001b..f9a7a19742 100644 --- a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/DataSourceService.java +++ b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/DataSourceService.java @@ -24,7 +24,7 @@ import org.bithon.server.datasource.query.Query; import org.bithon.server.datasource.query.ast.ExpressionNode; import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.storage.metrics.IMetricStorage; import org.bithon.server.web.service.WebServiceModuleEnabler; import org.springframework.context.annotation.Conditional; diff --git a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/IDataSourceApi.java b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/IDataSourceApi.java index e708513537..8bafa1932e 100644 --- a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/IDataSourceApi.java +++ b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/IDataSourceApi.java @@ -19,7 +19,7 @@ import jakarta.validation.constraints.Min; import lombok.Data; import org.bithon.server.datasource.ISchema; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; diff --git a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/impl/DataSourceApi.java b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/impl/DataSourceApi.java index 3d4e138c0f..be3b81ef6b 100644 --- a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/impl/DataSourceApi.java +++ b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/impl/DataSourceApi.java @@ -30,7 +30,7 @@ import org.bithon.server.datasource.query.IDataSourceReader; import org.bithon.server.datasource.query.Interval; import org.bithon.server.datasource.query.Query; -import org.bithon.server.datasource.query.pipeline.ColumnarTable; +import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.datasource.store.IDataStoreSpec; import org.bithon.server.discovery.client.DiscoveredServiceInvoker; import org.bithon.server.pipeline.tracing.sampler.ITraceSampler; From fa3cece2c9b6b26149b92cc1ece851a5650c9ac3 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 4 Jun 2025 23:59:03 +0800 Subject: [PATCH 12/22] Rename IQueryStep to IPhysicalPlan --- .../query/plan/physical/IPhysicalPlan.java | 8 +- .../query/plan/physical/IQueryStep.java | 31 - .../reader/jdbc/JdbcDataSourceReader.java | 13 +- .../jdbc/pipeline/JdbcPipelineBuilder.java | 12 +- .../reader/jdbc/pipeline/JdbcReadStep.java | 4 +- .../SlidingWindowAggregationStep.java | 8 +- .../metric/expression/api/MetricQueryApi.java | 12 +- .../pipeline/QueryPipelineBuilder.java | 18 +- .../pipeline/step/ArithmeticStep.java | 28 +- .../expression/pipeline/step/FilterStep.java | 18 +- .../pipeline/step/FunctionCallStep.java | 10 +- .../pipeline/step/LiteralQueryStep.java | 4 +- .../pipeline/step/MetricQueryStep.java | 4 +- .../pipeline/step/ArithmeticStepTest.java | 682 +++++++++--------- 14 files changed, 412 insertions(+), 440 deletions(-) delete mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IQueryStep.java diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java index c5811cd2f7..e3de5f3fcc 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java @@ -16,12 +16,16 @@ package org.bithon.server.datasource.query.plan.physical; + import java.util.concurrent.CompletableFuture; /** * @author frank.chen021@outlook.com - * @date 2025/6/4 23:49 + * @date 4/4/25 3:47 pm */ public interface IPhysicalPlan { - CompletableFuture execute() throws Exception; + + boolean isScalar(); + + CompletableFuture execute() throws Exception; } diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IQueryStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IQueryStep.java deleted file mode 100644 index 747b718ac6..0000000000 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IQueryStep.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2020 bithon.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bithon.server.datasource.query.plan.physical; - - -import java.util.concurrent.CompletableFuture; - -/** - * @author frank.chen021@outlook.com - * @date 4/4/25 3:47 pm - */ -public interface IQueryStep { - - boolean isScalar(); - - CompletableFuture execute() throws Exception; -} diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java index 1afe1554b7..3a8d989475 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java @@ -34,7 +34,6 @@ import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; import org.bithon.server.datasource.query.setting.QuerySettings; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.pipeline.JdbcPipelineBuilder; @@ -137,16 +136,16 @@ public ColumnarTable timeseries(Query query) { Interval interval = query.getInterval(); - IQueryStep queryStep = JdbcPipelineBuilder.builder() - .dslContext(dslContext) - .dialect(this.sqlDialect) - .selectStatement(selectStatement) - .interval(Interval.of(interval.getStartTime().floor(query.getInterval().getStep()), + IPhysicalPlan queryStep = JdbcPipelineBuilder.builder() + .dslContext(dslContext) + .dialect(this.sqlDialect) + .selectStatement(selectStatement) + .interval(Interval.of(interval.getStartTime().floor(query.getInterval().getStep()), interval.getEndTime(), interval.getStep(), null, null)) - .build(); + .build(); try { return queryStep.execute() diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java index d4fab2389b..9bb1a67111 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java @@ -24,7 +24,7 @@ import org.bithon.server.datasource.query.Order; import org.bithon.server.datasource.query.ast.ExpressionNode; import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.statement.ast.OrderByClause; import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; @@ -75,7 +75,7 @@ public JdbcPipelineBuilder interval(Interval interval) { return this; } - public IQueryStep build() { + public IPhysicalPlan build() { List windowFunctionSelectors = new ArrayList<>(); List inputColumns = new ArrayList<>(); List outputColumns = new ArrayList<>(); @@ -116,10 +116,10 @@ public IQueryStep build() { .toList() .toArray(new OrderByClause[0])); - IQueryStep readStep = new JdbcReadStep(dslContext, - dialect, - subQuery, - false); + IPhysicalPlan readStep = new JdbcReadStep(dslContext, + dialect, + subQuery, + false); return new SlidingWindowAggregationStep(orderBy.getIdentifier(), keys, diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java index b6d70a983c..0c0fac81a1 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java @@ -21,7 +21,7 @@ import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.physical.Column; import org.bithon.server.datasource.query.plan.physical.ColumnarTable; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; @@ -37,7 +37,7 @@ * @date 5/5/25 6:14 pm */ @Slf4j -public class JdbcReadStep implements IQueryStep { +public class JdbcReadStep implements IPhysicalPlan { private final DSLContext dslContext; private final SelectStatement selectStatement; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java index 1d4940d18a..fa71aa6a63 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java @@ -22,7 +22,7 @@ import org.bithon.server.datasource.query.Interval; import org.bithon.server.datasource.query.plan.physical.Column; import org.bithon.server.datasource.query.plan.physical.ColumnarTable; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import java.time.Duration; @@ -34,14 +34,14 @@ * @date 5/5/25 6:20 pm */ @Slf4j -public class SlidingWindowAggregationStep implements IQueryStep { +public class SlidingWindowAggregationStep implements IPhysicalPlan { private final String tsField; private final List keyFields; private final List valueFields; private final Duration window; private final List resultFields; private final Interval interval; - private final IQueryStep source; + private final IPhysicalPlan source; public SlidingWindowAggregationStep(String tsField, List keyFields, @@ -49,7 +49,7 @@ public SlidingWindowAggregationStep(String tsField, List resultFields, Duration window, Interval interval, - IQueryStep source) { + IPhysicalPlan source) { this.tsField = tsField; this.keyFields = keyFields; this.valueFields = valueFields; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java index 7466340c38..9b41291602 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java @@ -26,7 +26,7 @@ import org.bithon.component.commons.Experimental; import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import org.bithon.server.metric.expression.pipeline.QueryPipelineBuilder; import org.bithon.server.web.service.WebServiceModuleEnabler; @@ -87,11 +87,11 @@ public static class MetricQueryRequest { @Experimental @PostMapping("/api/metric/timeseries") public QueryResponse timeSeries(@Validated @RequestBody MetricQueryRequest request) throws Exception { - IQueryStep pipeline = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(request.getInterval()) - .condition(request.getCondition()) - .build(request.getExpression()); + IPhysicalPlan pipeline = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(request.getInterval()) + .condition(request.getCondition()) + .build(request.getExpression()); Duration step = request.getInterval().calculateStep(); TimeSpan start = request.getInterval().getStartISO8601(); diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java index a0daa59045..b2c53fcbaf 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java @@ -29,7 +29,7 @@ import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.utils.StringUtils; import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.metric.expression.ast.IMetricExpressionVisitor; import org.bithon.server.metric.expression.ast.MetricAggregateExpression; import org.bithon.server.metric.expression.ast.MetricExpectedExpression; @@ -79,7 +79,7 @@ public QueryPipelineBuilder settings(QueryPipelineBuilderSettings settings) { return this; } - public IQueryStep build(String expression) { + public IPhysicalPlan build(String expression) { IExpression expr = MetricExpressionASTBuilder.parse(expression); // Apply optimization like constant folding on parsed expression @@ -89,13 +89,13 @@ public IQueryStep build(String expression) { return this.build(expr); } - public IQueryStep build(IExpression expression) { + public IPhysicalPlan build(IExpression expression) { return expression.accept(new Builder()); } - private class Builder implements IMetricExpressionVisitor { + private class Builder implements IMetricExpressionVisitor { @Override - public IQueryStep visit(MetricAggregateExpression expression) { + public IPhysicalPlan visit(MetricAggregateExpression expression) { String filterExpression = Stream.of(expression.getWhereText(), condition) .filter(Objects::nonNull) .collect(Collectors.joining(" AND ")); @@ -185,7 +185,7 @@ public IQueryStep visit(FunctionCallExpression expression) { }*/ @Override - public IQueryStep visit(LiteralExpression expression) { + public IPhysicalPlan visit(LiteralExpression expression) { return new LiteralQueryStep(expression, Interval.of(intervalRequest.getStartISO8601(), intervalRequest.getEndISO8601(), intervalRequest.calculateStep(), @@ -193,7 +193,7 @@ public IQueryStep visit(LiteralExpression expression) { } @Override - public IQueryStep visit(ArithmeticExpression expression) { + public IPhysicalPlan visit(ArithmeticExpression expression) { return switch (expression.getType()) { case "+" -> new ArithmeticStep.Add(expression.getLhs().accept(this), expression.getRhs().accept(this)); case "-" -> new ArithmeticStep.Sub(expression.getLhs().accept(this), expression.getRhs().accept(this)); @@ -208,7 +208,7 @@ public IQueryStep visit(ArithmeticExpression expression) { * Push down the filter condition to the source step. */ @Override - public IQueryStep visit(ConditionalExpression expression) { + public IPhysicalPlan visit(ConditionalExpression expression) { if (settings.isPushdownPostFilter()) { IExpression lhs = expression.getLhs(); IExpression rhs = expression.getRhs(); @@ -236,7 +236,7 @@ public IQueryStep visit(ConditionalExpression expression) { } } - IQueryStep source = expression.getLhs().accept(this); + IPhysicalPlan source = expression.getLhs().accept(this); if (expression instanceof ComparisonExpression.LT) { return new FilterStep.LT( diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java index e5068377e9..c35073f20d 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java @@ -19,7 +19,7 @@ import org.bithon.server.datasource.query.plan.physical.Column; import org.bithon.server.datasource.query.plan.physical.ColumnarTable; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import java.util.ArrayList; @@ -35,9 +35,9 @@ * @author frank.chen021@outlook.com * @date 4/4/25 3:49 pm */ -public abstract class ArithmeticStep implements IQueryStep { - private final IQueryStep lhs; - private final IQueryStep rhs; +public abstract class ArithmeticStep implements IPhysicalPlan { + private final IPhysicalPlan lhs; + private final IPhysicalPlan rhs; private final String resultColumnName; /** @@ -46,7 +46,7 @@ public abstract class ArithmeticStep implements IQueryStep { private final Set retainedColumns; public static class Add extends ArithmeticStep { - public Add(IQueryStep left, IQueryStep right) { + public Add(IPhysicalPlan left, IPhysicalPlan right) { super(left, right, null); } @@ -57,12 +57,12 @@ int getOperatorIndex() { } public static class Sub extends ArithmeticStep { - public Sub(IQueryStep left, IQueryStep right) { + public Sub(IPhysicalPlan left, IPhysicalPlan right) { super(left, right, null); } - public Sub(IQueryStep left, - IQueryStep right, + public Sub(IPhysicalPlan left, + IPhysicalPlan right, String resultColumn, String... sourceColumns) { super(left, right, resultColumn, sourceColumns); @@ -75,7 +75,7 @@ int getOperatorIndex() { } public static class Mul extends ArithmeticStep { - public Mul(IQueryStep left, IQueryStep right) { + public Mul(IPhysicalPlan left, IPhysicalPlan right) { super(left, right, null); } @@ -86,12 +86,12 @@ int getOperatorIndex() { } public static class Div extends ArithmeticStep { - public Div(IQueryStep left, IQueryStep right) { + public Div(IPhysicalPlan left, IPhysicalPlan right) { super(left, right, null); } - public Div(IQueryStep left, - IQueryStep right, + public Div(IPhysicalPlan left, + IPhysicalPlan right, String resultColumn, String... sourceColumns) { super(left, right, resultColumn, sourceColumns); @@ -103,8 +103,8 @@ int getOperatorIndex() { } } - protected ArithmeticStep(IQueryStep left, - IQueryStep right, + protected ArithmeticStep(IPhysicalPlan left, + IPhysicalPlan right, String resultColumnName, String... retainedColumns) { this.lhs = left; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java index f17b792e30..a0cfcf8993 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java @@ -20,7 +20,7 @@ import org.bithon.component.commons.expression.IDataType; import org.bithon.server.datasource.query.plan.physical.Column; import org.bithon.server.datasource.query.plan.physical.ColumnarTable; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import org.bithon.server.metric.expression.ast.MetricExpectedExpression; @@ -32,11 +32,11 @@ * @author frank.chen021@outlook.com * @date 1/6/25 9:06 pm */ -public abstract class FilterStep implements IQueryStep { - protected final IQueryStep source; +public abstract class FilterStep implements IPhysicalPlan { + protected final IPhysicalPlan source; protected final MetricExpectedExpression expected; - protected FilterStep(IQueryStep source, MetricExpectedExpression expected) { + protected FilterStep(IPhysicalPlan source, MetricExpectedExpression expected) { this.source = source; this.expected = expected; } @@ -97,7 +97,7 @@ public CompletableFuture execute() throws Exception { protected abstract boolean filter(double actual, double expected); public static class LT extends FilterStep { - public LT(IQueryStep source, MetricExpectedExpression expected) { + public LT(IPhysicalPlan source, MetricExpectedExpression expected) { super(source, expected); } @@ -113,7 +113,7 @@ protected boolean filter(double actual, double expected) { } public static class LTE extends FilterStep { - public LTE(IQueryStep source, MetricExpectedExpression expected) { + public LTE(IPhysicalPlan source, MetricExpectedExpression expected) { super(source, expected); } @@ -129,7 +129,7 @@ protected boolean filter(double actual, double expected) { } public static class GT extends FilterStep { - public GT(IQueryStep source, MetricExpectedExpression expected) { + public GT(IPhysicalPlan source, MetricExpectedExpression expected) { super(source, expected); } @@ -145,7 +145,7 @@ protected boolean filter(double actual, double expected) { } public static class GTE extends FilterStep { - public GTE(IQueryStep source, MetricExpectedExpression expected) { + public GTE(IPhysicalPlan source, MetricExpectedExpression expected) { super(source, expected); } @@ -161,7 +161,7 @@ protected boolean filter(double actual, double expected) { } public static class NE extends FilterStep { - public NE(IQueryStep source, MetricExpectedExpression expected) { + public NE(IPhysicalPlan source, MetricExpectedExpression expected) { super(source, expected); } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java index 56789d9323..1f6678d35c 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java @@ -17,7 +17,7 @@ package org.bithon.server.metric.expression.pipeline.step; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import java.util.concurrent.CompletableFuture; @@ -29,10 +29,10 @@ * @author frank.chen021@outlook.com * @date 2/6/25 9:29 pm */ -public abstract class FunctionCallStep implements IQueryStep { - private final IQueryStep source; +public abstract class FunctionCallStep implements IPhysicalPlan { + private final IPhysicalPlan source; - public FunctionCallStep(IQueryStep source) { + public FunctionCallStep(IPhysicalPlan source) { this.source = source; } @@ -41,7 +41,7 @@ public boolean isScalar() { return source.isScalar(); } - public static FunctionCallStep apply(IQueryStep source, + public static FunctionCallStep apply(IPhysicalPlan source, Function function) { return new FunctionCallStep(source) { @Override diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStep.java index c4c019d56d..962d71acd8 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStep.java @@ -24,7 +24,7 @@ import org.bithon.server.datasource.query.plan.physical.Column; import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.datasource.query.plan.physical.DoubleColumn; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.plan.physical.LongColumn; import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; @@ -38,7 +38,7 @@ * @author frank.chen021@outlook.com * @date 4/4/25 9:36 pm */ -public class LiteralQueryStep implements IQueryStep { +public class LiteralQueryStep implements IPhysicalPlan { private final LiteralExpression expression; private final int size; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java index 4829c43579..6f2fd3569d 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java @@ -22,7 +22,7 @@ import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.TimestampSpec; import org.bithon.server.datasource.query.plan.physical.ColumnarTable; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; @@ -40,7 +40,7 @@ * @author frank.chen021@outlook.com * @date 4/4/25 3:48 pm */ -public class MetricQueryStep implements IQueryStep { +public class MetricQueryStep implements IPhysicalPlan { private final String dataSource; private final IntervalRequest interval; private final String filterExpression; diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java index 2c316d54fe..0a9dc4f59e 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java @@ -23,7 +23,7 @@ import org.bithon.server.datasource.query.plan.physical.Column; import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.datasource.query.plan.physical.DoubleColumn; -import org.bithon.server.datasource.query.plan.physical.IQueryStep; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.plan.physical.LongColumn; import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; import org.bithon.server.datasource.query.plan.physical.StringColumn; @@ -55,14 +55,14 @@ public void test_ScalarOverLiteral_Add_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -75,14 +75,14 @@ public void test_ScalarOverLiteral_Add_Long_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 3.3"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 3.3"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -95,14 +95,14 @@ public void test_ScalarOverLiteral_Add_Double_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 3.7))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -115,14 +115,14 @@ public void test_ScalarOverLiteral_Add_Double_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 10.5))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 2.2"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 2.2"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -135,14 +135,14 @@ public void test_ScalarOverSizeLiteral_Add_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5Mi"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5Mi"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -155,14 +155,14 @@ public void test_ScalarOverPercentageLiteral_Add_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 90%"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 90%"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -175,14 +175,14 @@ public void test_ScalarOverDurationLiteral_Add_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 1h"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 1h"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -195,14 +195,14 @@ public void test_ScalarOverLiteral_Sub_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -215,14 +215,14 @@ public void test_ScalarOverLiteral_Sub_Double_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 10.5))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 2.2"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 2.2"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -235,14 +235,14 @@ public void test_ScalarOverLiteral_Mul_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -255,14 +255,14 @@ public void test_ScalarOverLiteral_Mul_Double_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 5.5))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -275,14 +275,14 @@ public void test_ScalarOverLiteral_Mul_Long_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5.5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5.5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -295,14 +295,14 @@ public void test_ScalarOverLiteral_Mul_Double_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 3.5))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 3"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 3"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -315,14 +315,14 @@ public void test_ScalarOverLiteral_Div_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 10))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -335,14 +335,14 @@ public void test_ScalarOverLiteral_Div_Double_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 10))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -355,14 +355,14 @@ public void test_ScalarOverLiteral_Div_Long_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 10))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20.0"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20.0"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -375,14 +375,14 @@ public void test_ScalarOverLiteral_Div_Double_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 10.5))); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 3.0"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 3.0"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -412,15 +412,15 @@ public void test_VectorOverLiteral_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "5"); PipelineQueryResult response = evaluator.execute().get(); @@ -461,15 +461,15 @@ public void test_VectorOverLiteral_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + " 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -510,15 +510,15 @@ public void test_VectorOverLiteral_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "5"); PipelineQueryResult response = evaluator.execute().get(); @@ -559,15 +559,15 @@ public void test_VectorOverLiteral_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "5"); PipelineQueryResult response = evaluator.execute().get(); @@ -607,15 +607,15 @@ public void test_ScalarOverScalar_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -648,15 +648,15 @@ public void test_ScalarOverScalar_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -687,15 +687,15 @@ public void test_ScalarOverScalar_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -726,15 +726,15 @@ public void test_ScalarOverScalar_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -767,15 +767,15 @@ public void test_ScalarOverVector_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -818,15 +818,15 @@ public void test_ScalarOverVector_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -869,15 +869,15 @@ public void test_ScalarOverVector_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -920,15 +920,15 @@ public void test_ScalarOverVector_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -971,15 +971,15 @@ public void test_ScalarOverVector_Div_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1022,15 +1022,15 @@ public void test_ScalarOverVector_Div_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1071,15 +1071,15 @@ public void test_VectorOverScalar_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1120,15 +1120,15 @@ public void test_VectorOverScalar_Add_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1169,15 +1169,15 @@ public void test_VectorOverScalar_Add_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1218,15 +1218,15 @@ public void test_VectorOverScalar_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1267,15 +1267,15 @@ public void test_VectorOverScalar_Sub_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1316,15 +1316,15 @@ public void test_VectorOverScalar_Sub_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1365,15 +1365,15 @@ public void test_VectorOverScalar_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1414,15 +1414,15 @@ public void test_VectorOverScalar_Mul_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1463,15 +1463,15 @@ public void test_VectorOverScalar_Mul_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1513,15 +1513,15 @@ public void test_VectorOverScalar_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1563,15 +1563,15 @@ public void test_VectorOverScalar_Div_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1613,15 +1613,15 @@ public void test_VectorOverScalar_Div_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1665,15 +1665,15 @@ public void test_VectorOverVector_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1716,15 +1716,15 @@ public void test_VectorOverVector_Add_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1768,15 +1768,15 @@ public void test_VectorOverVector_Add_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1819,15 +1819,15 @@ public void test_VectorOverVector_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1870,15 +1870,15 @@ public void test_VectorOverVector_Sub_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1921,15 +1921,15 @@ public void test_VectorOverVector_Sub_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1972,15 +1972,15 @@ public void test_VectorOverVector_Sub_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2023,15 +2023,15 @@ public void test_VectorOverVector_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2074,15 +2074,15 @@ public void test_VectorOverVector_Mul_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2125,15 +2125,15 @@ public void test_VectorOverVector_Mul_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2177,15 +2177,15 @@ public void test_VectorOverVector_Mul_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2228,15 +2228,15 @@ public void test_VectorOverVector_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2279,15 +2279,15 @@ public void test_VectorOverVector_Div_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2330,15 +2330,15 @@ public void test_VectorOverVector_Div_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2381,15 +2381,15 @@ public void test_VectorOverVector_Div_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2432,15 +2432,15 @@ public void test_VectorOverVector_NoIntersection() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2491,15 +2491,15 @@ public void test_VectorOverVector_NoIntersection_2() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -2552,15 +2552,15 @@ public void test_VectorOverVector_NoIntersection_3() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -2608,15 +2608,15 @@ public void test_MultipleExpressions() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + "/ " + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + "* " @@ -2657,15 +2657,15 @@ public void test_RelativeComparison() throws Exception { }); - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) > -5%[-1d]"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) > -5%[-1d]"); PipelineQueryResult response = evaluator.execute() .get(); Assertions.assertEquals(2, response.getRows()); @@ -2706,15 +2706,15 @@ public void test_FilterStep_Double_GT() throws Exception { // Case 1, > 2 // { - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 2"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 2"); PipelineQueryResult response = evaluator.execute().get(); // all 3 records satisfy the filter condition @@ -2734,15 +2734,15 @@ public void test_FilterStep_Double_GT() throws Exception { // Case 2, > 3, two rows satisfy the filter condition // { - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 3"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 3"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(2, response.getRows()); @@ -2758,15 +2758,15 @@ public void test_FilterStep_Double_GT() throws Exception { // Case 3, > 4, 1 rows satisfies the filter condition // { - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 4"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 4"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(1, response.getRows()); @@ -2782,15 +2782,15 @@ public void test_FilterStep_Double_GT() throws Exception { // Case 4, > 5, 0 rows satisfies the filter condition // { - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 5"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 5"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(0, response.getRows()); @@ -2816,14 +2816,14 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 1, >= 2, all 3 rows satisfy the filter condition // { - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 2"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 2"); PipelineQueryResult response = evaluator.execute().get(); // all 3 records satisfy the filter condition @@ -2841,14 +2841,14 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 2, >= 3, all 3 rows satisfy the filter condition // { - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 3"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 3"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(3, response.getRows()); @@ -2865,15 +2865,15 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 3, >= 4, 2 rows satisfies the filter condition // { - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 4"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 4"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(2, response.getRows()); @@ -2889,15 +2889,15 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 4, >= 5, some rows satisfy the filter condition // { - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 5"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 5"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(1, response.getRows()); @@ -2912,14 +2912,14 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 5, >= 6, 0 rows satisfies the filter condition // { - IQueryStep evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = QueryPipelineBuilder.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 6"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 6"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(0, response.getRows()); From eff7aab1fb408b96d0e35c3e55b8eab7e4c5c22e Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Thu, 5 Jun 2025 21:22:01 +0800 Subject: [PATCH 13/22] Add physical plan/logical plan --- .../datasource/query/IDataSourceReader.java | 2 +- .../plan/logical/BaseLogicalPlanVisitor.java | 94 +++ .../query/plan/logical/ILogicalPlan.java | 8 + .../plan/logical/ILogicalPlanVisitor.java | 64 ++ .../query/plan/logical/LogicalAggregate.java | 11 +- .../query/plan/logical/LogicalBinaryOp.java | 8 +- .../query/plan/logical/LogicalFilter.java | 10 +- .../plan/logical/LogicalPlanSerializer.java | 102 +++ .../query/plan/logical/LogicalPlanner.java | 7 +- .../query/plan/logical/LogicalScalar.java | 8 +- .../query/plan/logical/LogicalTableScan.java | 11 +- .../query/plan/physical}/ArithmeticStep.java | 9 +- .../query/plan/physical}/ColumnOperator.java | 8 +- .../query/plan/physical/CompositeKey.java | 2 + .../query/plan/physical}/FilterStep.java | 28 +- .../plan/physical}/FunctionCallStep.java | 5 +- .../query/plan/physical}/HashJoiner.java | 7 +- .../query/plan/physical/IPhysicalPlan.java | 13 + .../plan/physical}/LiteralQueryStep.java | 13 +- .../query/plan/physical}/MetricQueryStep.java | 53 +- .../query/plan/physical/PhysicalPlanner.java | 61 ++ .../{plan/physical => result}/Column.java | 2 +- .../physical => result}/ColumnarTable.java | 2 +- .../physical => result}/DoubleColumn.java | 2 +- .../{plan/physical => result}/LongColumn.java | 2 +- .../PipelineQueryResult.java | 2 +- .../physical => result}/StringColumn.java | 2 +- .../query/pipeline/DoubleColumnTest.java | 4 +- .../query/pipeline/LongColumnTest.java | 4 +- .../query/pipeline/StringColumnTest.java | 4 +- .../reader/jdbc/JdbcDataSourceReader.java | 13 +- .../reader/jdbc/pipeline/JdbcReadStep.java | 6 +- .../jdbc/pipeline/JdbcTableScanStep.java | 97 +++ .../SlidingWindowAggregationStep.java | 6 +- .../pipeline/SlidingWindowAggregator.java | 4 +- .../jdbc/statement/JdbcPhysicalPlanner.java | 95 +++ .../jdbc/statement/ast/SelectorList.java | 4 + .../jdbc/SlidingWindowAggregatorTest.java | 4 +- .../reader/vm/VMDataSourceReader.java | 4 +- .../metric/expression/api/MetricQueryApi.java | 16 +- ...elineBuilder.java => PhysicalPlanner.java} | 50 +- .../expression/plan/LogicalPlanBuilder.java | 10 +- .../pipeline/step/ArithmeticStepTest.java | 694 +++++++++--------- .../pipeline/step/LiteralQueryStepTest.java | 3 +- .../builder/JdbcPhysicalPlannerTest.java | 66 ++ .../jdbc/tracing/reader/TraceJdbcReader.java | 4 +- .../server/storage/tracing/ITraceReader.java | 2 +- .../datasource/api/DataSourceService.java | 2 +- .../datasource/api/IDataSourceApi.java | 2 +- .../datasource/api/impl/DataSourceApi.java | 2 +- 50 files changed, 1126 insertions(+), 506 deletions(-) create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/BaseLogicalPlanVisitor.java create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlanVisitor.java create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanSerializer.java rename server/{metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step => datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical}/ArithmeticStep.java (97%) rename server/{metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step => datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical}/ColumnOperator.java (99%) rename server/{metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step => datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical}/FilterStep.java (82%) rename server/{metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step => datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical}/FunctionCallStep.java (89%) rename server/{metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step => datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical}/HashJoiner.java (94%) rename server/{metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step => datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical}/LiteralQueryStep.java (87%) rename server/{metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step => datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical}/MetricQueryStep.java (78%) create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanner.java rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{plan/physical => result}/Column.java (97%) rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{plan/physical => result}/ColumnarTable.java (98%) rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{plan/physical => result}/DoubleColumn.java (98%) rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{plan/physical => result}/LongColumn.java (98%) rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{plan/physical => result}/PipelineQueryResult.java (94%) rename server/datasource/common/src/main/java/org/bithon/server/datasource/query/{plan/physical => result}/StringColumn.java (98%) create mode 100644 server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcTableScanStep.java create mode 100644 server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java rename server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/{QueryPipelineBuilder.java => PhysicalPlanner.java} (90%) create mode 100644 server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java index 747da9794b..6fcc514db9 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java @@ -17,9 +17,9 @@ package org.bithon.server.datasource.query; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.query.result.ColumnarTable; import java.util.List; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/BaseLogicalPlanVisitor.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/BaseLogicalPlanVisitor.java new file mode 100644 index 0000000000..3459447ea9 --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/BaseLogicalPlanVisitor.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +/** + * Base implementation of ILogicalPlanVisitor that provides default behavior + * and utility methods for common visitor operations. + *

+ * This class can be extended to implement specific visitor functionality + * without having to implement all visitor methods from scratch. + * + * @param the return type of the visitor methods + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:31 + */ +public abstract class BaseLogicalPlanVisitor implements ILogicalPlanVisitor { + + /** + * Default implementation for visiting table scans. + * Can be overridden by subclasses to provide specific behavior. + */ + @Override + public T visitTableScan(LogicalTableScan tableScan) { + return defaultVisit(tableScan); + } + + /** + * Default implementation for visiting aggregates. + * Recursively visit the input plan. + * It Can be overridden by subclasses to provide specific behavior. + */ + @Override + public T visitAggregate(LogicalAggregate aggregate) { + aggregate.input().accept(this); + return defaultVisit(aggregate); + } + + /** + * Default implementation for visiting binary operations. + * Recursively visits both left and right operands. + * It Can be overridden by subclasses to provide specific behavior. + */ + @Override + public T visitBinaryOp(LogicalBinaryOp binaryOp) { + binaryOp.left().accept(this); + binaryOp.right().accept(this); + return defaultVisit(binaryOp); + } + + /** + * Default implementation for visiting scalars. + * Can be overridden by subclasses to provide specific behavior. + */ + @Override + public T visitScalar(LogicalScalar scalar) { + return defaultVisit(scalar); + } + + /** + * Default implementation for visiting filters. + * Recursively visits both left and right operands. + * It Can be overridden by subclasses to provide specific behavior. + */ + @Override + public T visitFilter(LogicalFilter filter) { + filter.left().accept(this); + filter.right().accept(this); + return defaultVisit(filter); + } + + /** + * Default behavior for all visit methods. + * Subclasses can override this to provide common default behavior, + * or override individual visit methods for specific behavior. + * + * @param plan the logical plan being visited + * @return the default result + */ + protected abstract T defaultVisit(ILogicalPlan plan); +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlan.java index b9594a712c..ae247d779e 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlan.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlan.java @@ -23,5 +23,13 @@ public sealed interface ILogicalPlan permits LogicalTableScan, LogicalAggregate, LogicalBinaryOp, LogicalScalar, LogicalFilter { + + /** + * Accept a visitor for the visitor pattern implementation + * @param visitor the visitor to accept + * @param the return type of the visitor + * @return the result of visiting this logical plan + */ + T accept(ILogicalPlanVisitor visitor); } diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlanVisitor.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlanVisitor.java new file mode 100644 index 0000000000..285e2fdc50 --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/ILogicalPlanVisitor.java @@ -0,0 +1,64 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +/** + * Visitor interface for the ILogicalPlan hierarchy. + * Implements the visitor pattern to allow operations on logical plans + * without modifying the plan classes themselves. + * + * @param the return type of the visitor methods + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:31 + */ +public interface ILogicalPlanVisitor { + + /** + * Visit a LogicalTableScan node + * @param tableScan the table to visit + * @return the result of visiting the table scan + */ + T visitTableScan(LogicalTableScan tableScan); + + /** + * Visit a LogicalAggregate node + * @param aggregate the aggregate to visit + * @return the result of visiting the aggregate + */ + T visitAggregate(LogicalAggregate aggregate); + + /** + * Visit a LogicalBinaryOp node + * @param binaryOp the binary operation to visit + * @return the result of visiting the binary operation + */ + T visitBinaryOp(LogicalBinaryOp binaryOp); + + /** + * Visit a LogicalScalar node + * @param scalar the scalar to visit + * @return the result of visiting the scalar + */ + T visitScalar(LogicalScalar scalar); + + /** + * Visit a LogicalFilter node + * @param filter the filter to visit + * @return the result of visiting the filter + */ + T visitFilter(LogicalFilter filter); +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java index 7b4902c706..791dff2fdb 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java @@ -16,6 +16,8 @@ package org.bithon.server.datasource.query.plan.logical; +import org.bithon.server.datasource.column.IColumn; + import java.util.List; /** @@ -25,7 +27,14 @@ public record LogicalAggregate( String func, // e.g., "sum", "avg" ILogicalPlan input, // e.g., table scan - List groupBy // e.g., ["appName"] + IColumn field, + List groupBy, + List orderBy ) implements ILogicalPlan { + + @Override + public T accept(ILogicalPlanVisitor visitor) { + return visitor.visitAggregate(this); + } } diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalBinaryOp.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalBinaryOp.java index 16a72da2c7..55f6553109 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalBinaryOp.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalBinaryOp.java @@ -24,4 +24,10 @@ public record LogicalBinaryOp( ILogicalPlan left, BinaryOp op, ILogicalPlan right -) implements ILogicalPlan {} +) implements ILogicalPlan { + + @Override + public T accept(ILogicalPlanVisitor visitor) { + return visitor.visitBinaryOp(this); + } +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalFilter.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalFilter.java index 77272cdbfe..f8fe270a72 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalFilter.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalFilter.java @@ -16,8 +16,6 @@ package org.bithon.server.datasource.query.plan.logical; -import org.bithon.server.metric.expression.ast.PredicateEnum; - /** * @author frank.chen021@outlook.com * @date 2025/6/4 23:34 @@ -26,4 +24,10 @@ public record LogicalFilter( ILogicalPlan left, PredicateEnum op, ILogicalPlan right -) implements ILogicalPlan {} +) implements ILogicalPlan { + + @Override + public T accept(ILogicalPlanVisitor visitor) { + return visitor.visitFilter(this); + } +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanSerializer.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanSerializer.java new file mode 100644 index 0000000000..df88f988dc --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanSerializer.java @@ -0,0 +1,102 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.logical; + +/** + * Concrete visitor implementation that converts logical plans to string representation. + * This serves as an example of how to implement and use the visitor pattern. + * + * @author frank.chen021@outlook.com + * @date 2025/6/4 23:31 + */ +public class LogicalPlanSerializer implements ILogicalPlanVisitor { + + private int indentLevel = 0; + private static final String INDENT = " "; + + @Override + public String visitTableScan(LogicalTableScan tableScan) { + return indent() + "TableScan(" + + "table='" + tableScan.table() + "', " + + "filters=" + (tableScan.filter() != null ? tableScan.filter().toString() : "null") + + ")"; + } + + @Override + public String visitAggregate(LogicalAggregate aggregate) { + StringBuilder sb = new StringBuilder(); + sb.append(indent()).append("Aggregate("); + sb.append("func='").append(aggregate.func()).append("', "); + sb.append("groupBy=").append(aggregate.groupBy()).append(",\n"); + + indentLevel++; + String inputStr = aggregate.input().accept(this); + indentLevel--; + + sb.append(inputStr).append("\n"); + sb.append(indent()).append(")"); + return sb.toString(); + } + + @Override + public String visitBinaryOp(LogicalBinaryOp binaryOp) { + StringBuilder sb = new StringBuilder(); + sb.append(indent()).append("BinaryOp(op=").append(binaryOp.op()).append(",\n"); + + indentLevel++; + String leftStr = binaryOp.left().accept(this); + String rightStr = binaryOp.right().accept(this); + indentLevel--; + + sb.append(leftStr).append(",\n"); + sb.append(rightStr).append("\n"); + sb.append(indent()).append(")"); + return sb.toString(); + } + + @Override + public String visitScalar(LogicalScalar scalar) { + return indent() + "Scalar(value=" + scalar.value() + ")"; + } + + @Override + public String visitFilter(LogicalFilter filter) { + StringBuilder sb = new StringBuilder(); + sb.append(indent()).append("Filter(op=").append(filter.op()).append(",\n"); + + indentLevel++; + String leftStr = filter.left().accept(this); + String rightStr = filter.right().accept(this); + indentLevel--; + + sb.append(leftStr).append(",\n"); + sb.append(rightStr).append("\n"); + sb.append(indent()).append(")"); + return sb.toString(); + } + + private String indent() { + return INDENT.repeat(indentLevel); + } + + /** + * Utility method to convert any logical plan to string representation + */ + public static String toString(ILogicalPlan plan) { + return plan.accept(new LogicalPlanSerializer()); + } +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanner.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanner.java index b964f0fb71..13496c3d92 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanner.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanner.java @@ -16,17 +16,14 @@ package org.bithon.server.datasource.query.plan.logical; -import org.bithon.component.commons.expression.IExpression; -import org.bithon.server.metric.expression.plan.ILogicalPlan; -import org.bithon.server.metric.expression.plan.LogicalPlanBuilder; - /** * @author frank.chen021@outlook.com * @date 2025/6/4 23:36 */ public class LogicalPlanner { + /* public static ILogicalPlan plan(IExpression expression) { return expression.accept(new LogicalPlanBuilder()); - } + }*/ } diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java index 8837ee419c..bc0e5b2405 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java @@ -20,4 +20,10 @@ * @author frank.chen021@outlook.com * @date 2025/6/4 23:33 */ -public record LogicalScalar(double value) implements ILogicalPlan {} +public record LogicalScalar(double value) implements ILogicalPlan { + + @Override + public T accept(ILogicalPlanVisitor visitor) { + return visitor.visitScalar(this); + } +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java index 9991f5791d..3cc7557bb5 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java @@ -17,6 +17,9 @@ package org.bithon.server.datasource.query.plan.logical; import org.bithon.component.commons.expression.IExpression; +import org.bithon.server.datasource.query.ast.Selector; + +import java.util.List; /** * @author frank.chen021@outlook.com @@ -24,6 +27,12 @@ */ public record LogicalTableScan( String table, - IExpression labelMatchers // = filters like {job="abc"} + List selectorList, + IExpression filter // = filters like {job="abc"} ) implements ILogicalPlan { + + @Override + public T accept(ILogicalPlanVisitor visitor) { + return visitor.visitTableScan(this); + } } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ArithmeticStep.java similarity index 97% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ArithmeticStep.java index c35073f20d..7b32b96516 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ArithmeticStep.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package org.bithon.server.metric.expression.pipeline.step; +package org.bithon.server.datasource.query.plan.physical; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; -import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; +import org.bithon.server.datasource.query.result.PipelineQueryResult; import java.util.ArrayList; import java.util.Arrays; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ColumnOperator.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ColumnOperator.java similarity index 99% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ColumnOperator.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ColumnOperator.java index b53de4788d..1c59130541 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/ColumnOperator.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ColumnOperator.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package org.bithon.server.metric.expression.pipeline.step; +package org.bithon.server.datasource.query.plan.physical; import org.bithon.component.commons.expression.IDataTypeIndex; import org.bithon.component.commons.utils.StringUtils; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.DoubleColumn; -import org.bithon.server.datasource.query.plan.physical.LongColumn; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.DoubleColumn; +import org.bithon.server.datasource.query.result.LongColumn; /** * @author frank.chen021@outlook.com diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/CompositeKey.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/CompositeKey.java index 0abd2ec9af..85633d4ead 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/CompositeKey.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/CompositeKey.java @@ -17,6 +17,8 @@ package org.bithon.server.datasource.query.plan.physical; +import org.bithon.server.datasource.query.result.Column; + import java.util.Arrays; import java.util.List; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/FilterStep.java similarity index 82% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/FilterStep.java index a0cfcf8993..0224cd0b4e 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FilterStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/FilterStep.java @@ -14,15 +14,13 @@ * limitations under the License. */ -package org.bithon.server.metric.expression.pipeline.step; +package org.bithon.server.datasource.query.plan.physical; import org.bithon.component.commons.expression.IDataType; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; -import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; -import org.bithon.server.metric.expression.ast.MetricExpectedExpression; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; +import org.bithon.server.datasource.query.result.PipelineQueryResult; import java.util.concurrent.CompletableFuture; @@ -34,9 +32,9 @@ */ public abstract class FilterStep implements IPhysicalPlan { protected final IPhysicalPlan source; - protected final MetricExpectedExpression expected; + protected final Number expected; - protected FilterStep(IPhysicalPlan source, MetricExpectedExpression expected) { + protected FilterStep(IPhysicalPlan source, Number expected) { this.source = source; this.expected = expected; } @@ -56,7 +54,7 @@ public CompletableFuture execute() throws Exception { int filteredRowCount = 0; int[] filteredRows = new int[result.getTable().rowCount()]; if (column.getDataType() == IDataType.LONG) { - long expectedValue = ((Number) expected.getExpected().getValue()).longValue(); + long expectedValue = this.expected.longValue(); for (int i = 0, size = column.size(); i < size; i++) { if (filter(column.getLong(i), expectedValue)) { @@ -64,7 +62,7 @@ public CompletableFuture execute() throws Exception { } } } else if (column.getDataType() == IDataType.DOUBLE) { - double expectedValue = ((Number) expected.getExpected().getValue()).doubleValue(); + double expectedValue = this.expected.doubleValue(); for (int i = 0, size = column.size(); i < size; i++) { if (filter(column.getDouble(i), expectedValue)) { @@ -97,7 +95,7 @@ public CompletableFuture execute() throws Exception { protected abstract boolean filter(double actual, double expected); public static class LT extends FilterStep { - public LT(IPhysicalPlan source, MetricExpectedExpression expected) { + public LT(IPhysicalPlan source, Number expected) { super(source, expected); } @@ -113,7 +111,7 @@ protected boolean filter(double actual, double expected) { } public static class LTE extends FilterStep { - public LTE(IPhysicalPlan source, MetricExpectedExpression expected) { + public LTE(IPhysicalPlan source, Number expected) { super(source, expected); } @@ -129,7 +127,7 @@ protected boolean filter(double actual, double expected) { } public static class GT extends FilterStep { - public GT(IPhysicalPlan source, MetricExpectedExpression expected) { + public GT(IPhysicalPlan source, Number expected) { super(source, expected); } @@ -145,7 +143,7 @@ protected boolean filter(double actual, double expected) { } public static class GTE extends FilterStep { - public GTE(IPhysicalPlan source, MetricExpectedExpression expected) { + public GTE(IPhysicalPlan source, Number expected) { super(source, expected); } @@ -161,7 +159,7 @@ protected boolean filter(double actual, double expected) { } public static class NE extends FilterStep { - public NE(IPhysicalPlan source, MetricExpectedExpression expected) { + public NE(IPhysicalPlan source, Number expected) { super(source, expected); } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/FunctionCallStep.java similarity index 89% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/FunctionCallStep.java index 1f6678d35c..d878d10e8a 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/FunctionCallStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/FunctionCallStep.java @@ -14,11 +14,10 @@ * limitations under the License. */ -package org.bithon.server.metric.expression.pipeline.step; +package org.bithon.server.datasource.query.plan.physical; -import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; +import org.bithon.server.datasource.query.result.PipelineQueryResult; import java.util.concurrent.CompletableFuture; import java.util.function.Function; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/HashJoiner.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/HashJoiner.java similarity index 94% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/HashJoiner.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/HashJoiner.java index 3d2e736168..c253e83ee4 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/HashJoiner.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/HashJoiner.java @@ -14,12 +14,11 @@ * limitations under the License. */ -package org.bithon.server.metric.expression.pipeline.step; +package org.bithon.server.datasource.query.plan.physical; import org.bithon.component.commons.utils.CollectionUtils; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; -import org.bithon.server.datasource.query.plan.physical.CompositeKey; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; import java.util.ArrayList; import java.util.HashMap; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java index e3de5f3fcc..14276144c7 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java @@ -17,6 +17,8 @@ package org.bithon.server.datasource.query.plan.physical; +import org.bithon.server.datasource.query.result.PipelineQueryResult; + import java.util.concurrent.CompletableFuture; /** @@ -27,5 +29,16 @@ public interface IPhysicalPlan { boolean isScalar(); + default String serializeToText() { + StringBuilder builder = new StringBuilder(); + serializer(builder); + return builder.toString(); + } + + default void serializer(StringBuilder builder) { + builder.append(this.getClass().getSimpleName()); + builder.append("\n"); + } + CompletableFuture execute() throws Exception; } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LiteralQueryStep.java similarity index 87% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStep.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LiteralQueryStep.java index 962d71acd8..3be02f7a94 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LiteralQueryStep.java @@ -14,19 +14,18 @@ * limitations under the License. */ -package org.bithon.server.metric.expression.pipeline.step; +package org.bithon.server.datasource.query.plan.physical; import org.bithon.component.commons.expression.IDataType; import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; -import org.bithon.server.datasource.query.plan.physical.DoubleColumn; -import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.LongColumn; -import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; +import org.bithon.server.datasource.query.result.DoubleColumn; +import org.bithon.server.datasource.query.result.LongColumn; +import org.bithon.server.datasource.query.result.PipelineQueryResult; import java.util.Collections; import java.util.List; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/MetricQueryStep.java similarity index 78% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/MetricQueryStep.java index 6f2fd3569d..a3d8e301d0 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/step/MetricQueryStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/MetricQueryStep.java @@ -14,25 +14,14 @@ * limitations under the License. */ -package org.bithon.server.metric.expression.pipeline.step; +package org.bithon.server.datasource.query.plan.physical; import org.bithon.component.commons.utils.CollectionUtils; import org.bithon.component.commons.utils.HumanReadableDuration; -import org.bithon.server.commons.time.TimeSpan; -import org.bithon.server.datasource.TimestampSpec; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; -import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; -import org.bithon.server.web.service.datasource.api.IDataSourceApi; -import org.bithon.server.web.service.datasource.api.IntervalRequest; -import org.bithon.server.web.service.datasource.api.QueryField; -import org.bithon.server.web.service.datasource.api.QueryRequest; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import org.bithon.server.datasource.query.Interval; +import org.bithon.server.datasource.query.result.PipelineQueryResult; + import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -42,12 +31,10 @@ */ public class MetricQueryStep implements IPhysicalPlan { private final String dataSource; - private final IntervalRequest interval; + private final Interval interval; private final String filterExpression; - private final List fields; private final Set groupBy; private final HumanReadableDuration offset; - private final IDataSourceApi dataSourceApi; private final boolean isScalar; // Make sure the evaluation is executed ONLY ONCE when the expression is referenced multiple times @@ -57,10 +44,8 @@ private MetricQueryStep(Builder builder) { this.dataSource = builder.dataSource; this.interval = builder.interval; this.filterExpression = builder.filterExpression; - this.fields = builder.fields; this.groupBy = builder.groupBy; this.offset = builder.offset; - this.dataSourceApi = builder.dataSourceApi; this.isScalar = computeIsScalar(); } @@ -70,19 +55,17 @@ public static Builder builder() { public static class Builder { private String dataSource; - private IntervalRequest interval; + private Interval interval; private String filterExpression; - private List fields; private Set groupBy; private HumanReadableDuration offset; - private IDataSourceApi dataSourceApi; public Builder dataSource(String dataSource) { this.dataSource = dataSource; return this; } - public Builder interval(IntervalRequest interval) { + public Builder interval(Interval interval) { this.interval = interval; return this; } @@ -92,11 +75,6 @@ public Builder filterExpression(String filterExpression) { return this; } - public Builder fields(List fields) { - this.fields = fields; - return this; - } - public Builder groupBy(Set groupBy) { this.groupBy = groupBy; return this; @@ -107,11 +85,6 @@ public Builder offset(HumanReadableDuration offset) { return this; } - public Builder dataSourceApi(IDataSourceApi dataSourceApi) { - this.dataSourceApi = dataSourceApi; - return this; - } - public MetricQueryStep build() { return new MetricQueryStep(this); } @@ -123,6 +96,7 @@ private boolean computeIsScalar() { return false; } + /* if (interval.getBucketCount() != null && interval.getBucketCount() == 1) { // ONLY one bucket is requested, the result set is a scalar return true; @@ -130,8 +104,9 @@ private boolean computeIsScalar() { TimeSpan start = interval.getStartISO8601(); TimeSpan end = interval.getEndISO8601(); - long intervalLength = (end.getMilliseconds() - start.getMilliseconds()) / 1000; - return interval.getStep() != null && interval.getStep() == intervalLength; + long intervalLength = (end.getMilliseconds() - start.getMilliseconds()) / 1000;= + return interval.getStep() != null && interval.getStep() == intervalLength;*/ + return true; } @Override @@ -145,7 +120,9 @@ public CompletableFuture execute() { synchronized (this) { if (cachedResponse == null) { cachedResponse = CompletableFuture.supplyAsync(() -> { - try { + return null; + /*try { + QueryRequest queryRequest = QueryRequest.builder() .dataSource(dataSource) .interval(interval) @@ -173,7 +150,7 @@ public CompletableFuture execute() { .build(); } catch (IOException e) { throw new RuntimeException(e); - } + }*/ }); } } diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanner.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanner.java new file mode 100644 index 0000000000..6fa606e320 --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanner.java @@ -0,0 +1,61 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.physical; + + +import org.bithon.server.datasource.query.plan.logical.ILogicalPlanVisitor; +import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; +import org.bithon.server.datasource.query.plan.logical.LogicalBinaryOp; +import org.bithon.server.datasource.query.plan.logical.LogicalFilter; +import org.bithon.server.datasource.query.plan.logical.LogicalScalar; +import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; + +/** + * @author frank.chen021@outlook.com + * @date 4/4/25 3:53 pm + */ +public class PhysicalPlanner implements ILogicalPlanVisitor { + + + @Override + public IPhysicalPlan visitTableScan(LogicalTableScan tableScan) { + return null; + } + + @Override + public IPhysicalPlan visitAggregate(LogicalAggregate aggregate) { + return null; + } + + @Override + public IPhysicalPlan visitBinaryOp(LogicalBinaryOp binaryOp) { + // Implementation for visiting binary operations + return null; // Placeholder + } + + @Override + public IPhysicalPlan visitFilter(LogicalFilter filter) { + // Implementation for visiting filters + return null; // Placeholder + } + + @Override + public IPhysicalPlan visitScalar(LogicalScalar scalar) { + // Implementation for visiting scalars + return null; // Placeholder + } +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/Column.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/Column.java similarity index 97% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/Column.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/Column.java index 5fb13aa2f6..ba197c346a 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/Column.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/Column.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.plan.physical; +package org.bithon.server.datasource.query.result; import org.bithon.component.commons.expression.IDataType; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ColumnarTable.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/ColumnarTable.java similarity index 98% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ColumnarTable.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/ColumnarTable.java index aea81c48ec..9c30ea1306 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ColumnarTable.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/ColumnarTable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.plan.physical; +package org.bithon.server.datasource.query.result; import java.util.ArrayList; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/DoubleColumn.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/DoubleColumn.java similarity index 98% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/DoubleColumn.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/DoubleColumn.java index 38fa1c1632..b758110f40 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/DoubleColumn.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/DoubleColumn.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.plan.physical; +package org.bithon.server.datasource.query.result; import org.bithon.component.commons.expression.IDataType; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LongColumn.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/LongColumn.java similarity index 98% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LongColumn.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/LongColumn.java index 2874acc1d9..3c669b2871 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LongColumn.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/LongColumn.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.plan.physical; +package org.bithon.server.datasource.query.result; import org.bithon.component.commons.expression.IDataType; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PipelineQueryResult.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/PipelineQueryResult.java similarity index 94% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PipelineQueryResult.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/PipelineQueryResult.java index a1cae6ca5e..5659761b05 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PipelineQueryResult.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/PipelineQueryResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.plan.physical; +package org.bithon.server.datasource.query.result; import lombok.AllArgsConstructor; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/StringColumn.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/StringColumn.java similarity index 98% rename from server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/StringColumn.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/StringColumn.java index 39ceff6378..6a4b6b8b40 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/StringColumn.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/result/StringColumn.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.datasource.query.plan.physical; +package org.bithon.server.datasource.query.result; import org.bithon.component.commons.expression.IDataType; diff --git a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/DoubleColumnTest.java b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/DoubleColumnTest.java index c83927e158..20046b8054 100644 --- a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/DoubleColumnTest.java +++ b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/DoubleColumnTest.java @@ -16,8 +16,8 @@ package org.bithon.server.datasource.query.pipeline; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.DoubleColumn; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.DoubleColumn; import org.junit.jupiter.api.Test; import java.util.BitSet; diff --git a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/LongColumnTest.java b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/LongColumnTest.java index 1ef925b7db..e9a1607bf9 100644 --- a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/LongColumnTest.java +++ b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/LongColumnTest.java @@ -16,8 +16,8 @@ package org.bithon.server.datasource.query.pipeline; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.LongColumn; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.LongColumn; import org.junit.jupiter.api.Test; import java.util.BitSet; diff --git a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/StringColumnTest.java b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/StringColumnTest.java index 0377e81bc8..88f63738bf 100644 --- a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/StringColumnTest.java +++ b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/pipeline/StringColumnTest.java @@ -16,8 +16,8 @@ package org.bithon.server.datasource.query.pipeline; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.StringColumn; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.StringColumn; import org.junit.jupiter.api.Test; import java.util.BitSet; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java index 3a8d989475..b152c8e3f4 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java @@ -32,11 +32,12 @@ import org.bithon.server.datasource.query.Query; import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.query.result.ColumnarTable; import org.bithon.server.datasource.query.setting.QuerySettings; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.pipeline.JdbcPipelineBuilder; +import org.bithon.server.datasource.reader.jdbc.statement.JdbcPhysicalPlanner; import org.bithon.server.datasource.reader.jdbc.statement.ast.LimitClause; import org.bithon.server.datasource.reader.jdbc.statement.ast.OrderByClause; import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; @@ -117,7 +118,7 @@ public JdbcDataSourceReader(DSLContext dslContext, ISqlDialect sqlDialect, Query @Override public IPhysicalPlan plan(ILogicalPlan plan) { - return IDataSourceReader.super.plan(plan); + return plan.accept(new JdbcPhysicalPlanner(this.dslContext, this.sqlDialect)); } @Override @@ -141,10 +142,10 @@ public ColumnarTable timeseries(Query query) { .dialect(this.sqlDialect) .selectStatement(selectStatement) .interval(Interval.of(interval.getStartTime().floor(query.getInterval().getStep()), - interval.getEndTime(), - interval.getStep(), - null, - null)) + interval.getEndTime(), + interval.getStep(), + null, + null)) .build(); try { diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java index 0c0fac81a1..adaa8ee534 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java @@ -19,10 +19,10 @@ import lombok.extern.slf4j.Slf4j; import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; +import org.bithon.server.datasource.query.result.PipelineQueryResult; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; import org.jooq.Cursor; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcTableScanStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcTableScanStep.java new file mode 100644 index 0000000000..b38b15a720 --- /dev/null +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcTableScanStep.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.reader.jdbc.pipeline; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.bithon.component.commons.expression.IExpression; +import org.bithon.server.datasource.query.ast.Selector; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; +import org.bithon.server.datasource.query.result.PipelineQueryResult; +import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; +import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; +import org.bithon.server.datasource.reader.jdbc.statement.ast.TableIdentifier; +import org.jooq.Cursor; +import org.jooq.DSLContext; +import org.jooq.Record; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/5 19:58 + */ +@Slf4j +public class JdbcTableScanStep implements IPhysicalPlan { + private final DSLContext dslContext; + private final boolean isScalar; + private final ISqlDialect sqlDialect; + + @Getter + private SelectStatement selectStatement; + + public JdbcTableScanStep(DSLContext dslContext, + ISqlDialect sqlDialect, + boolean isScalar, + SelectStatement selectStatement) { + this.dslContext = dslContext; + this.sqlDialect = sqlDialect; + this.isScalar = isScalar; + this.selectStatement = selectStatement; + } + + @Override + public boolean isScalar() { + return this.isScalar; + } + + @Override + public void serializer(StringBuilder builder) { + builder.append("TableScan\n"); + builder.append(selectStatement.toSQL(this.sqlDialect)); + builder.append("\n"); + } + + @Override + public CompletableFuture execute() throws Exception { + return CompletableFuture.supplyAsync(() -> { + ColumnarTable resultTable = new ColumnarTable(); + for (Selector selector : selectStatement.getSelectorList().getSelectors()) { + resultTable.addColumn(Column.create(selector.getOutputName(), selector.getDataType(), 1024)); + } + List resultColumns = resultTable.getColumns(); + + String sql = selectStatement.toSQL(this.sqlDialect); + + log.info("Executing {}", sql); + try (Cursor cursor = dslContext.fetchLazy(sql)) { + for (Record record : cursor) { + for (int i = 0; i < resultColumns.size(); i++) { + Column column = resultColumns.get(i); + column.addObject(record.get(i)); + } + } + return PipelineQueryResult.builder() + .table(resultTable) + .build(); + } + }); + } +} diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java index fa71aa6a63..6acdeda184 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java @@ -20,10 +20,10 @@ import lombok.extern.slf4j.Slf4j; import org.bithon.server.datasource.TimestampSpec; import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; +import org.bithon.server.datasource.query.result.PipelineQueryResult; import java.time.Duration; import java.util.List; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregator.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregator.java index 6dc8ffe945..7c9653e5c4 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregator.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregator.java @@ -16,9 +16,9 @@ package org.bithon.server.datasource.reader.jdbc.pipeline; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; import org.bithon.server.datasource.query.plan.physical.CompositeKey; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; import java.time.Duration; import java.util.ArrayList; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java new file mode 100644 index 0000000000..fff2f10b3f --- /dev/null +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.reader.jdbc.statement; + +import org.bithon.component.commons.expression.LogicalExpression; +import org.bithon.component.commons.utils.StringUtils; +import org.bithon.server.datasource.query.ast.ExpressionNode; +import org.bithon.server.datasource.query.ast.Selector; +import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; +import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; +import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.query.plan.physical.PhysicalPlanner; +import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; +import org.bithon.server.datasource.reader.jdbc.pipeline.JdbcTableScanStep; +import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; +import org.bithon.server.datasource.reader.jdbc.statement.ast.TableIdentifier; +import org.bithon.server.datasource.reader.jdbc.statement.builder.SelectStatementBuilder; +import org.jooq.DSLContext; + +import java.util.List; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/5 20:14 + */ +public class JdbcPhysicalPlanner extends PhysicalPlanner { + private final DSLContext dslContext; + private final ISqlDialect sqlDialect; + + public JdbcPhysicalPlanner(DSLContext dslContext, ISqlDialect sqlDialect) { + this.dslContext = dslContext; + this.sqlDialect = sqlDialect; + } + + public IPhysicalPlan plan(ILogicalPlan logicalPlan) { + return logicalPlan.accept(this); + } + + @Override + public IPhysicalPlan visitAggregate(LogicalAggregate aggregate) { + IPhysicalPlan physicalPlan = aggregate.input().accept(this); + if (physicalPlan instanceof JdbcTableScanStep tableScan) { + // PUSH the aggregate down to the table scan step + //JdbcTableScanStep tableScanStep + return new JdbcTableScanStep(dslContext, + sqlDialect, + false, + plan(sqlDialect, tableScan.getSelectStatement(), aggregate)); + } + + return super.visitAggregate(aggregate); + } + + @Override + public IPhysicalPlan visitTableScan(LogicalTableScan tableScan) { + SelectStatement selectStatement = new SelectStatement(); + selectStatement.getFrom().setExpression(new TableIdentifier(tableScan.table())); + selectStatement.getFrom().setAlias(tableScan.table()); + selectStatement.getWhere().and(tableScan.filter()); + selectStatement.getSelectorList().addAll(tableScan.selectorList()); + + return new JdbcTableScanStep( + dslContext, + sqlDialect, + false, + selectStatement + ); + } + + private SelectStatement plan(ISqlDialect sqlDialect, SelectStatement select, LogicalAggregate aggregate) { + SelectStatementBuilder builder = new SelectStatementBuilder(); + return builder.sqlDialect(sqlDialect) + .fields(List.of(new Selector(new ExpressionNode(StringUtils.format("%s(%s)", aggregate.func(), aggregate.field().getName())), + aggregate.field().getName(), + aggregate.field().getDataType()))) + .filter(LogicalExpression.create(LogicalExpression.AND.OP, select.getWhere().getExpressions())) + .groupBy(aggregate.groupBy()) + .build(); + } +} diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/ast/SelectorList.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/ast/SelectorList.java index 888b328b43..9bfadf7177 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/ast/SelectorList.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/ast/SelectorList.java @@ -86,6 +86,10 @@ public Selector add(IASTNode columnExpression, Alias output, IDataType dataType) return selector; } + public void addAll(List columns) { + selectors.addAll(columns); + } + public int size() { return selectors.size(); } diff --git a/server/datasource/reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc/SlidingWindowAggregatorTest.java b/server/datasource/reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc/SlidingWindowAggregatorTest.java index 2b08f4d9fc..fde6fc6535 100644 --- a/server/datasource/reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc/SlidingWindowAggregatorTest.java +++ b/server/datasource/reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc/SlidingWindowAggregatorTest.java @@ -17,8 +17,8 @@ package org.bithon.server.datasource.reader.jdbc; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; import org.bithon.server.datasource.reader.jdbc.pipeline.SlidingWindowAggregator; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/server/datasource/reader-vm/src/main/java/org/bithon/server/datasource/reader/vm/VMDataSourceReader.java b/server/datasource/reader-vm/src/main/java/org/bithon/server/datasource/reader/vm/VMDataSourceReader.java index 5416b1e884..de87e7e739 100644 --- a/server/datasource/reader-vm/src/main/java/org/bithon/server/datasource/reader/vm/VMDataSourceReader.java +++ b/server/datasource/reader-vm/src/main/java/org/bithon/server/datasource/reader/vm/VMDataSourceReader.java @@ -32,8 +32,8 @@ import org.bithon.server.datasource.query.Query; import org.bithon.server.datasource.query.ast.ExpressionNode; import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java index 9b41291602..728800a678 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java @@ -25,10 +25,10 @@ import lombok.NoArgsConstructor; import org.bithon.component.commons.Experimental; import org.bithon.server.commons.time.TimeSpan; -import org.bithon.server.datasource.query.plan.physical.Column; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; -import org.bithon.server.metric.expression.pipeline.QueryPipelineBuilder; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.PipelineQueryResult; +import org.bithon.server.metric.expression.pipeline.PhysicalPlanner; import org.bithon.server.web.service.WebServiceModuleEnabler; import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; @@ -87,11 +87,11 @@ public static class MetricQueryRequest { @Experimental @PostMapping("/api/metric/timeseries") public QueryResponse timeSeries(@Validated @RequestBody MetricQueryRequest request) throws Exception { - IPhysicalPlan pipeline = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(request.getInterval()) - .condition(request.getCondition()) - .build(request.getExpression()); + IPhysicalPlan pipeline = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(request.getInterval()) + .condition(request.getCondition()) + .build(request.getExpression()); Duration step = request.getInterval().calculateStep(); TimeSpan start = request.getInterval().getStartISO8601(); diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java similarity index 90% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java rename to server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java index b2c53fcbaf..234d05b815 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java @@ -17,11 +17,6 @@ package org.bithon.server.metric.expression.pipeline; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import org.bithon.component.commons.expression.ArithmeticExpression; import org.bithon.component.commons.expression.ComparisonExpression; import org.bithon.component.commons.expression.ConditionalExpression; @@ -29,52 +24,56 @@ import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.utils.StringUtils; import org.bithon.server.datasource.query.Interval; +import org.bithon.server.datasource.query.plan.physical.ArithmeticStep; +import org.bithon.server.datasource.query.plan.physical.FilterStep; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.query.plan.physical.LiteralQueryStep; +import org.bithon.server.datasource.query.plan.physical.MetricQueryStep; import org.bithon.server.metric.expression.ast.IMetricExpressionVisitor; import org.bithon.server.metric.expression.ast.MetricAggregateExpression; import org.bithon.server.metric.expression.ast.MetricExpectedExpression; import org.bithon.server.metric.expression.ast.MetricExpressionASTBuilder; import org.bithon.server.metric.expression.ast.MetricExpressionOptimizer; import org.bithon.server.metric.expression.ast.PredicateEnum; -import org.bithon.server.metric.expression.pipeline.step.ArithmeticStep; -import org.bithon.server.metric.expression.pipeline.step.FilterStep; -import org.bithon.server.metric.expression.pipeline.step.LiteralQueryStep; -import org.bithon.server.metric.expression.pipeline.step.MetricQueryStep; import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; import org.bithon.server.web.service.datasource.api.QueryField; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + /** * @author frank.chen021@outlook.com * @date 4/4/25 3:53 pm */ -public class QueryPipelineBuilder { +public class PhysicalPlanner { private IDataSourceApi dataSourceApi; private IntervalRequest intervalRequest; private String condition; private QueryPipelineBuilderSettings settings = QueryPipelineBuilderSettings.DEFAULT; - public static QueryPipelineBuilder builder() { - return new QueryPipelineBuilder(); + public static PhysicalPlanner builder() { + return new PhysicalPlanner(); } - public QueryPipelineBuilder dataSourceApi(IDataSourceApi dataSourceApi) { + public PhysicalPlanner dataSourceApi(IDataSourceApi dataSourceApi) { this.dataSourceApi = dataSourceApi; return this; } - public QueryPipelineBuilder intervalRequest(IntervalRequest intervalRequest) { + public PhysicalPlanner intervalRequest(IntervalRequest intervalRequest) { this.intervalRequest = intervalRequest; return this; } - public QueryPipelineBuilder condition(String condition) { + public PhysicalPlanner condition(String condition) { this.condition = condition; return this; } - public QueryPipelineBuilder settings(QueryPipelineBuilderSettings settings) { + public PhysicalPlanner settings(QueryPipelineBuilderSettings settings) { this.settings = settings; return this; } @@ -108,15 +107,17 @@ public IPhysicalPlan visit(MetricAggregateExpression expression) { .dataSource(expression.getFrom()) .filterExpression(filterExpression) .groupBy(expression.getGroupBy()) + /* .fields(List.of(new QueryField(metricField.getName(), metricField.getField(), null, expr))) .interval(intervalRequest) - .dataSourceApi(dataSourceApi) + .dataSourceApi(dataSourceApi)*/ .build(); MetricQueryStep base = MetricQueryStep.builder() .dataSource(expression.getFrom()) .filterExpression(filterExpression) .groupBy(expression.getGroupBy()) + /* .fields(List.of(new QueryField( // Use offset AS the output name expression.getOffset().toString(), @@ -125,7 +126,7 @@ public IPhysicalPlan visit(MetricAggregateExpression expression) { expr))) .interval(intervalRequest) .offset(expression.getOffset()) - .dataSourceApi(dataSourceApi) + .dataSourceApi(dataSourceApi)*/ .build(); // @@ -149,9 +150,10 @@ public IPhysicalPlan visit(MetricAggregateExpression expression) { .dataSource(expression.getFrom()) .filterExpression(filterExpression) .groupBy(expression.getGroupBy()) + /* .fields(List.of(expression.getMetric())) .interval(intervalRequest) - .dataSourceApi(dataSourceApi) + .dataSourceApi(dataSourceApi)*/ .build(); } } @@ -241,31 +243,31 @@ public IPhysicalPlan visit(ConditionalExpression expression) { if (expression instanceof ComparisonExpression.LT) { return new FilterStep.LT( source, - (MetricExpectedExpression) expression.getRhs() + (Number) ((MetricExpectedExpression) expression.getRhs()).getExpected().getValue() ); } if (expression instanceof ComparisonExpression.LTE) { return new FilterStep.LTE( source, - (MetricExpectedExpression) expression.getRhs() + (Number) ((MetricExpectedExpression) expression.getRhs()).getExpected().getValue() ); } if (expression instanceof ComparisonExpression.GT) { return new FilterStep.GT( source, - (MetricExpectedExpression) expression.getRhs() + (Number) ((MetricExpectedExpression) expression.getRhs()).getExpected().getValue() ); } if (expression instanceof ComparisonExpression.GTE) { return new FilterStep.GTE( source, - (MetricExpectedExpression) expression.getRhs() + (Number) ((MetricExpectedExpression) expression.getRhs()).getExpected().getValue() ); } if (expression instanceof ComparisonExpression.NE) { return new FilterStep.NE( source, - (MetricExpectedExpression) expression.getRhs() + (Number) ((MetricExpectedExpression) expression.getRhs()).getExpected().getValue() ); } throw new UnsupportedOperationException("Unsupported conditional expression: " + expression.getType()); diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java index 9db3b8c7d8..a0cdcff059 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java @@ -17,6 +17,7 @@ package org.bithon.server.metric.expression.plan; import org.bithon.component.commons.utils.CollectionUtils; +import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; @@ -25,6 +26,7 @@ import org.bithon.server.metric.expression.ast.MetricSelectExpression; import java.util.ArrayList; +import java.util.List; /** * @author frank.chen021@outlook.com @@ -35,6 +37,7 @@ class LogicalPlanBuilder implements IMetricExpressionVisitor { public ILogicalPlan visit(MetricSelectExpression expression) { return new LogicalTableScan( expression.getFrom(), + List.of(new Selector(expression.getMetric(), expression.getMetric(), expression.getDataType())), expression.getLabelSelectorExpression() ); } @@ -44,8 +47,13 @@ public ILogicalPlan visit(MetricAggregateExpression expression) { return new LogicalAggregate( expression.getMetric().getAggregator(), new LogicalTableScan(expression.getFrom(), + List.of(new Selector(expression.getMetric().getName(), + expression.getMetric().getName(), + expression.getDataType())), expression.getLabelSelectorExpression()), - CollectionUtils.isEmpty(expression.getGroupBy()) ? new ArrayList<>() : new ArrayList<>(expression.getGroupBy()) + null, + CollectionUtils.isEmpty(expression.getGroupBy()) ? new ArrayList<>() : new ArrayList<>(expression.getGroupBy()), + new ArrayList<>() ); } } diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java index 0a9dc4f59e..c29474dc58 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java @@ -20,14 +20,14 @@ import org.bithon.component.commons.utils.HumanReadableDuration; import org.bithon.component.commons.utils.HumanReadableNumber; import org.bithon.server.commons.time.TimeSpan; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; -import org.bithon.server.datasource.query.plan.physical.DoubleColumn; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.LongColumn; -import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; -import org.bithon.server.datasource.query.plan.physical.StringColumn; -import org.bithon.server.metric.expression.pipeline.QueryPipelineBuilder; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; +import org.bithon.server.datasource.query.result.DoubleColumn; +import org.bithon.server.datasource.query.result.LongColumn; +import org.bithon.server.datasource.query.result.PipelineQueryResult; +import org.bithon.server.datasource.query.result.StringColumn; +import org.bithon.server.metric.expression.pipeline.PhysicalPlanner; import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; import org.bithon.server.web.service.datasource.api.QueryRequest; @@ -55,14 +55,14 @@ public void test_ScalarOverLiteral_Add_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -75,14 +75,14 @@ public void test_ScalarOverLiteral_Add_Long_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 3.3"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 3.3"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -95,14 +95,14 @@ public void test_ScalarOverLiteral_Add_Double_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 3.7))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -115,14 +115,14 @@ public void test_ScalarOverLiteral_Add_Double_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 10.5))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 2.2"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 2.2"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -135,14 +135,14 @@ public void test_ScalarOverSizeLiteral_Add_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5Mi"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5Mi"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -155,14 +155,14 @@ public void test_ScalarOverPercentageLiteral_Add_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 90%"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 90%"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -175,14 +175,14 @@ public void test_ScalarOverDurationLiteral_Add_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 1h"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 1h"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -195,14 +195,14 @@ public void test_ScalarOverLiteral_Sub_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -215,14 +215,14 @@ public void test_ScalarOverLiteral_Sub_Double_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 10.5))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 2.2"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 2.2"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -235,14 +235,14 @@ public void test_ScalarOverLiteral_Mul_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -255,14 +255,14 @@ public void test_ScalarOverLiteral_Mul_Double_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 5.5))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -275,14 +275,14 @@ public void test_ScalarOverLiteral_Mul_Long_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 1))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5.5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5.5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -295,14 +295,14 @@ public void test_ScalarOverLiteral_Mul_Double_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 3.5))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 3"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 3"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -315,14 +315,14 @@ public void test_ScalarOverLiteral_Div_Long_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 10))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 5"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -335,14 +335,14 @@ public void test_ScalarOverLiteral_Div_Double_Long() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 10))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -355,14 +355,14 @@ public void test_ScalarOverLiteral_Div_Long_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), LongColumn.of("activeThreads", 10))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20.0"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20.0"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -375,14 +375,14 @@ public void test_ScalarOverLiteral_Div_Double_Double() throws Exception { .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), DoubleColumn.of("activeThreads", 10.5))); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 3.0"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 3.0"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("value"); @@ -412,15 +412,15 @@ public void test_VectorOverLiteral_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "5"); PipelineQueryResult response = evaluator.execute().get(); @@ -461,15 +461,15 @@ public void test_VectorOverLiteral_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + " 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -510,15 +510,15 @@ public void test_VectorOverLiteral_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "5"); PipelineQueryResult response = evaluator.execute().get(); @@ -559,15 +559,15 @@ public void test_VectorOverLiteral_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "5"); PipelineQueryResult response = evaluator.execute().get(); @@ -607,15 +607,15 @@ public void test_ScalarOverScalar_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -648,15 +648,15 @@ public void test_ScalarOverScalar_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -687,15 +687,15 @@ public void test_ScalarOverScalar_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -726,15 +726,15 @@ public void test_ScalarOverScalar_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -767,15 +767,15 @@ public void test_ScalarOverVector_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -818,15 +818,15 @@ public void test_ScalarOverVector_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -869,15 +869,15 @@ public void test_ScalarOverVector_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -920,15 +920,15 @@ public void test_ScalarOverVector_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -971,15 +971,15 @@ public void test_ScalarOverVector_Div_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1022,15 +1022,15 @@ public void test_ScalarOverVector_Div_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1071,15 +1071,15 @@ public void test_VectorOverScalar_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1120,15 +1120,15 @@ public void test_VectorOverScalar_Add_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1169,15 +1169,15 @@ public void test_VectorOverScalar_Add_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1218,15 +1218,15 @@ public void test_VectorOverScalar_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1267,15 +1267,15 @@ public void test_VectorOverScalar_Sub_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1316,15 +1316,15 @@ public void test_VectorOverScalar_Sub_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1365,15 +1365,15 @@ public void test_VectorOverScalar_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1414,15 +1414,15 @@ public void test_VectorOverScalar_Mul_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1463,15 +1463,15 @@ public void test_VectorOverScalar_Mul_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1513,15 +1513,15 @@ public void test_VectorOverScalar_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1563,15 +1563,15 @@ public void test_VectorOverScalar_Div_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1613,15 +1613,15 @@ public void test_VectorOverScalar_Div_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1665,15 +1665,15 @@ public void test_VectorOverVector_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1716,15 +1716,15 @@ public void test_VectorOverVector_Add_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1768,15 +1768,15 @@ public void test_VectorOverVector_Add_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1819,15 +1819,15 @@ public void test_VectorOverVector_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1870,15 +1870,15 @@ public void test_VectorOverVector_Sub_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1921,15 +1921,15 @@ public void test_VectorOverVector_Sub_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1972,15 +1972,15 @@ public void test_VectorOverVector_Sub_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2023,15 +2023,15 @@ public void test_VectorOverVector_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2074,15 +2074,15 @@ public void test_VectorOverVector_Mul_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2125,15 +2125,15 @@ public void test_VectorOverVector_Mul_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2177,15 +2177,15 @@ public void test_VectorOverVector_Mul_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2228,15 +2228,15 @@ public void test_VectorOverVector_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2279,15 +2279,15 @@ public void test_VectorOverVector_Div_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2330,15 +2330,15 @@ public void test_VectorOverVector_Div_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2381,15 +2381,15 @@ public void test_VectorOverVector_Div_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2432,15 +2432,15 @@ public void test_VectorOverVector_NoIntersection() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2491,15 +2491,15 @@ public void test_VectorOverVector_NoIntersection_2() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -2552,15 +2552,15 @@ public void test_VectorOverVector_NoIntersection_3() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -2608,15 +2608,15 @@ public void test_MultipleExpressions() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + "/ " + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + "* " @@ -2657,15 +2657,15 @@ public void test_RelativeComparison() throws Exception { }); - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) > -5%[-1d]"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) > -5%[-1d]"); PipelineQueryResult response = evaluator.execute() .get(); Assertions.assertEquals(2, response.getRows()); @@ -2706,15 +2706,15 @@ public void test_FilterStep_Double_GT() throws Exception { // Case 1, > 2 // { - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 2"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 2"); PipelineQueryResult response = evaluator.execute().get(); // all 3 records satisfy the filter condition @@ -2734,15 +2734,15 @@ public void test_FilterStep_Double_GT() throws Exception { // Case 2, > 3, two rows satisfy the filter condition // { - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 3"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 3"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(2, response.getRows()); @@ -2758,15 +2758,15 @@ public void test_FilterStep_Double_GT() throws Exception { // Case 3, > 4, 1 rows satisfies the filter condition // { - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 4"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 4"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(1, response.getRows()); @@ -2782,15 +2782,15 @@ public void test_FilterStep_Double_GT() throws Exception { // Case 4, > 5, 0 rows satisfies the filter condition // { - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 5"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 5"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(0, response.getRows()); @@ -2816,14 +2816,14 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 1, >= 2, all 3 rows satisfy the filter condition // { - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 2"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 2"); PipelineQueryResult response = evaluator.execute().get(); // all 3 records satisfy the filter condition @@ -2841,14 +2841,14 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 2, >= 3, all 3 rows satisfy the filter condition // { - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 3"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 3"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(3, response.getRows()); @@ -2865,15 +2865,15 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 3, >= 4, 2 rows satisfies the filter condition // { - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 4"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 4"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(2, response.getRows()); @@ -2889,15 +2889,15 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 4, >= 5, some rows satisfy the filter condition // { - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 5"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 5"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(1, response.getRows()); @@ -2912,14 +2912,14 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 5, >= 6, 0 rows satisfies the filter condition // { - IPhysicalPlan evaluator = QueryPipelineBuilder.builder() - .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() + IPhysicalPlan evaluator = PhysicalPlanner.builder() + .dataSourceApi(dataSourceApi) + .intervalRequest(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 6"); + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 6"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(0, response.getRows()); diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStepTest.java index 38c6b1301a..74c7748d0f 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStepTest.java @@ -21,7 +21,8 @@ import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.plan.physical.PipelineQueryResult; +import org.bithon.server.datasource.query.plan.physical.LiteralQueryStep; +import org.bithon.server.datasource.query.result.PipelineQueryResult; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java new file mode 100644 index 0000000000..1d0292ad84 --- /dev/null +++ b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.storage.jdbc.common.statement.builder; + +import org.bithon.component.commons.expression.ComparisonExpression; +import org.bithon.component.commons.expression.IDataType; +import org.bithon.component.commons.expression.IdentifierExpression; +import org.bithon.component.commons.expression.LiteralExpression; +import org.bithon.component.commons.expression.LogicalExpression; +import org.bithon.server.datasource.query.ast.Selector; +import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; +import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.reader.h2.H2SqlDialect; +import org.bithon.server.datasource.reader.jdbc.statement.JdbcPhysicalPlanner; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/5 20:58 + */ +public class JdbcPhysicalPlannerTest { + @Test + public void testTableScanPlan() { + ILogicalPlan logicalPlan = new LogicalTableScan("test_table", + List.of(new Selector("a1", "a1", IDataType.DOUBLE)), + null); + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, new H2SqlDialect()).plan(logicalPlan); + Assertions.assertEquals(""" + TableScan + SELECT "a1" AS "a1" + FROM "test_table" AS "test_table" + """, physicalPlan.serializeToText()); + } + + @Test + public void testTableScanPlanWithFilter() { + ILogicalPlan logicalPlan = new LogicalTableScan("test_table", + List.of(new Selector("a1", "a1", IDataType.DOUBLE)), + new LogicalExpression.AND(new ComparisonExpression.EQ(IdentifierExpression.of("a1"), LiteralExpression.ofString("jacky")))); + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, new H2SqlDialect()).plan(logicalPlan); + Assertions.assertEquals(""" + TableScan + SELECT "a1" AS "a1" + FROM "test_table" AS "test_table" + WHERE ("a1" = 'jacky') + """, physicalPlan.serializeToText()); + } +} diff --git a/server/storage-jdbc/src/main/java/org/bithon/server/storage/jdbc/tracing/reader/TraceJdbcReader.java b/server/storage-jdbc/src/main/java/org/bithon/server/storage/jdbc/tracing/reader/TraceJdbcReader.java index 154faa3b7d..52bdc106de 100644 --- a/server/storage-jdbc/src/main/java/org/bithon/server/storage/jdbc/tracing/reader/TraceJdbcReader.java +++ b/server/storage-jdbc/src/main/java/org/bithon/server/storage/jdbc/tracing/reader/TraceJdbcReader.java @@ -39,8 +39,8 @@ import org.bithon.server.datasource.query.Order; import org.bithon.server.datasource.query.OrderBy; import org.bithon.server.datasource.query.Query; -import org.bithon.server.datasource.query.plan.physical.Column; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; import org.bithon.server.datasource.query.setting.QuerySettings; import org.bithon.server.datasource.reader.jdbc.JdbcDataSourceReader; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; diff --git a/server/storage/src/main/java/org/bithon/server/storage/tracing/ITraceReader.java b/server/storage/src/main/java/org/bithon/server/storage/tracing/ITraceReader.java index 06d333886e..96de19ba63 100644 --- a/server/storage/src/main/java/org/bithon/server/storage/tracing/ITraceReader.java +++ b/server/storage/src/main/java/org/bithon/server/storage/tracing/ITraceReader.java @@ -22,7 +22,7 @@ import org.bithon.server.datasource.query.IDataSourceReader; import org.bithon.server.datasource.query.Limit; import org.bithon.server.datasource.query.OrderBy; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.result.ColumnarTable; import org.bithon.server.storage.tracing.mapping.TraceIdMapping; import java.sql.Timestamp; diff --git a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/DataSourceService.java b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/DataSourceService.java index f9a7a19742..356c409495 100644 --- a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/DataSourceService.java +++ b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/DataSourceService.java @@ -24,7 +24,7 @@ import org.bithon.server.datasource.query.Query; import org.bithon.server.datasource.query.ast.ExpressionNode; import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.result.ColumnarTable; import org.bithon.server.storage.metrics.IMetricStorage; import org.bithon.server.web.service.WebServiceModuleEnabler; import org.springframework.context.annotation.Conditional; diff --git a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/IDataSourceApi.java b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/IDataSourceApi.java index 8bafa1932e..f462bc037f 100644 --- a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/IDataSourceApi.java +++ b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/IDataSourceApi.java @@ -19,7 +19,7 @@ import jakarta.validation.constraints.Min; import lombok.Data; import org.bithon.server.datasource.ISchema; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.result.ColumnarTable; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; diff --git a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/impl/DataSourceApi.java b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/impl/DataSourceApi.java index be3b81ef6b..d6df14c8f9 100644 --- a/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/impl/DataSourceApi.java +++ b/server/web-service/src/main/java/org/bithon/server/web/service/datasource/api/impl/DataSourceApi.java @@ -30,7 +30,7 @@ import org.bithon.server.datasource.query.IDataSourceReader; import org.bithon.server.datasource.query.Interval; import org.bithon.server.datasource.query.Query; -import org.bithon.server.datasource.query.plan.physical.ColumnarTable; +import org.bithon.server.datasource.query.result.ColumnarTable; import org.bithon.server.datasource.store.IDataStoreSpec; import org.bithon.server.discovery.client.DiscoveredServiceInvoker; import org.bithon.server.pipeline.tracing.sampler.ITraceSampler; From 26ddfa938bebf7e7024be26abbdaa2c40a628d64 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Sat, 7 Jun 2025 22:53:11 +0800 Subject: [PATCH 14/22] Reuse JdbcReadStep --- .../datasource/query/IDataSourceReader.java | 3 +- .../query/plan/logical/LogicalAggregate.java | 10 +- .../reader/jdbc/JdbcDataSourceReader.java | 5 +- .../jdbc/pipeline/JdbcPipelineBuilder.java | 6 +- .../reader/jdbc/pipeline/JdbcReadStep.java | 22 ++-- .../jdbc/pipeline/JdbcTableScanStep.java | 97 --------------- .../jdbc/statement/JdbcPhysicalPlanner.java | 41 ++++--- .../builder/JdbcPhysicalPlannerTest.java | 116 ++++++++++++++++-- 8 files changed, 163 insertions(+), 137 deletions(-) delete mode 100644 server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcTableScanStep.java diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java index 6fcc514db9..865f72f8f8 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java @@ -17,6 +17,7 @@ package org.bithon.server.datasource.query; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.bithon.server.datasource.ISchema; import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.result.ColumnarTable; @@ -30,7 +31,7 @@ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public interface IDataSourceReader extends AutoCloseable { - default IPhysicalPlan plan(ILogicalPlan plan) { + default IPhysicalPlan plan(ISchema schema, Interval interval, ILogicalPlan plan) { throw new UnsupportedOperationException("This data source does not support logical plan execution"); } diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java index 791dff2fdb..94d369b982 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalAggregate.java @@ -25,8 +25,14 @@ * @date 2025/6/4 23:32 */ public record LogicalAggregate( - String func, // e.g., "sum", "avg" - ILogicalPlan input, // e.g., table scan + /** + * The function to be applied for aggregation, e.g., "SUM", "AVG", etc. + */ + String func, + ILogicalPlan input, + /** + * The field to be aggregated. + */ IColumn field, List groupBy, List orderBy diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java index b152c8e3f4..a934feb36a 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java @@ -24,6 +24,7 @@ import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.utils.Preconditions; import org.bithon.component.commons.utils.StringUtils; +import org.bithon.server.datasource.ISchema; import org.bithon.server.datasource.query.IDataSourceReader; import org.bithon.server.datasource.query.Interval; import org.bithon.server.datasource.query.Limit; @@ -117,8 +118,8 @@ public JdbcDataSourceReader(DSLContext dslContext, ISqlDialect sqlDialect, Query } @Override - public IPhysicalPlan plan(ILogicalPlan plan) { - return plan.accept(new JdbcPhysicalPlanner(this.dslContext, this.sqlDialect)); + public IPhysicalPlan plan(ISchema schema, Interval interval, ILogicalPlan plan) { + return new JdbcPhysicalPlanner(this.dslContext, this.sqlDialect, schema).plan(interval, plan); } @Override diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java index 9bb1a67111..ddadff1e7d 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java @@ -93,7 +93,7 @@ public IPhysicalPlan build() { } if (windowFunctionSelectors.isEmpty()) { - return new JdbcReadStep(dslContext, dialect, selectStatement, false); + return new JdbcReadStep(dslContext, dialect, selectStatement); } WindowFunctionExpression windowFunctionExpression = (WindowFunctionExpression) ((ExpressionNode) windowFunctionSelectors.get(0).getSelectExpression()).getParsedExpression(); @@ -118,8 +118,8 @@ public IPhysicalPlan build() { IPhysicalPlan readStep = new JdbcReadStep(dslContext, dialect, - subQuery, - false); + subQuery + ); return new SlidingWindowAggregationStep(orderBy.getIdentifier(), keys, diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java index adaa8ee534..942f82d370 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java @@ -17,6 +17,7 @@ package org.bithon.server.datasource.reader.jdbc.pipeline; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; @@ -40,21 +41,28 @@ public class JdbcReadStep implements IPhysicalPlan { private final DSLContext dslContext; - private final SelectStatement selectStatement; private final String sql; - private final boolean isScalar; + + @Getter + private final SelectStatement selectStatement; public JdbcReadStep(DSLContext dslContext, ISqlDialect sqlDialect, - SelectStatement selectStatement, - boolean isScalar) { + SelectStatement selectStatement) { this.dslContext = dslContext; this.selectStatement = selectStatement; - this.isScalar = isScalar; - this.sql = selectStatement.toSQL(sqlDialect); } + @Override + public void serializer(StringBuilder builder) { + String stepName = this.getClass().getSimpleName(); + builder.append(stepName).append('\n'); + for (String line : this.sql.split("\\r?\\n")) { + builder.append(" ").append(line).append("\n"); + } + } + @Override public String toString() { return this.sql; @@ -62,7 +70,7 @@ public String toString() { @Override public boolean isScalar() { - return this.isScalar; + return false; } @Override diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcTableScanStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcTableScanStep.java deleted file mode 100644 index b38b15a720..0000000000 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcTableScanStep.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2020 bithon.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bithon.server.datasource.reader.jdbc.pipeline; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.bithon.component.commons.expression.IExpression; -import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.result.Column; -import org.bithon.server.datasource.query.result.ColumnarTable; -import org.bithon.server.datasource.query.result.PipelineQueryResult; -import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; -import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; -import org.bithon.server.datasource.reader.jdbc.statement.ast.TableIdentifier; -import org.jooq.Cursor; -import org.jooq.DSLContext; -import org.jooq.Record; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * @author frank.chen021@outlook.com - * @date 2025/6/5 19:58 - */ -@Slf4j -public class JdbcTableScanStep implements IPhysicalPlan { - private final DSLContext dslContext; - private final boolean isScalar; - private final ISqlDialect sqlDialect; - - @Getter - private SelectStatement selectStatement; - - public JdbcTableScanStep(DSLContext dslContext, - ISqlDialect sqlDialect, - boolean isScalar, - SelectStatement selectStatement) { - this.dslContext = dslContext; - this.sqlDialect = sqlDialect; - this.isScalar = isScalar; - this.selectStatement = selectStatement; - } - - @Override - public boolean isScalar() { - return this.isScalar; - } - - @Override - public void serializer(StringBuilder builder) { - builder.append("TableScan\n"); - builder.append(selectStatement.toSQL(this.sqlDialect)); - builder.append("\n"); - } - - @Override - public CompletableFuture execute() throws Exception { - return CompletableFuture.supplyAsync(() -> { - ColumnarTable resultTable = new ColumnarTable(); - for (Selector selector : selectStatement.getSelectorList().getSelectors()) { - resultTable.addColumn(Column.create(selector.getOutputName(), selector.getDataType(), 1024)); - } - List resultColumns = resultTable.getColumns(); - - String sql = selectStatement.toSQL(this.sqlDialect); - - log.info("Executing {}", sql); - try (Cursor cursor = dslContext.fetchLazy(sql)) { - for (Record record : cursor) { - for (int i = 0; i < resultColumns.size(); i++) { - Column column = resultColumns.get(i); - column.addObject(record.get(i)); - } - } - return PipelineQueryResult.builder() - .table(resultTable) - .build(); - } - }); - } -} diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java index fff2f10b3f..010ebb2240 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java @@ -18,6 +18,8 @@ import org.bithon.component.commons.expression.LogicalExpression; import org.bithon.component.commons.utils.StringUtils; +import org.bithon.server.datasource.ISchema; +import org.bithon.server.datasource.query.Interval; import org.bithon.server.datasource.query.ast.ExpressionNode; import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; @@ -26,7 +28,7 @@ import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.plan.physical.PhysicalPlanner; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; -import org.bithon.server.datasource.reader.jdbc.pipeline.JdbcTableScanStep; +import org.bithon.server.datasource.reader.jdbc.pipeline.JdbcReadStep; import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; import org.bithon.server.datasource.reader.jdbc.statement.ast.TableIdentifier; import org.bithon.server.datasource.reader.jdbc.statement.builder.SelectStatementBuilder; @@ -41,26 +43,35 @@ public class JdbcPhysicalPlanner extends PhysicalPlanner { private final DSLContext dslContext; private final ISqlDialect sqlDialect; + private final ISchema schema; + private Interval interval; - public JdbcPhysicalPlanner(DSLContext dslContext, ISqlDialect sqlDialect) { + public JdbcPhysicalPlanner(DSLContext dslContext, + ISqlDialect sqlDialect, + ISchema schema) { this.dslContext = dslContext; this.sqlDialect = sqlDialect; + this.schema = schema; } - public IPhysicalPlan plan(ILogicalPlan logicalPlan) { + public IPhysicalPlan plan(Interval interval, ILogicalPlan logicalPlan) { + this.interval = interval; return logicalPlan.accept(this); } @Override public IPhysicalPlan visitAggregate(LogicalAggregate aggregate) { IPhysicalPlan physicalPlan = aggregate.input().accept(this); - if (physicalPlan instanceof JdbcTableScanStep tableScan) { + if (physicalPlan instanceof JdbcReadStep jdbcReadStep) { // PUSH the aggregate down to the table scan step - //JdbcTableScanStep tableScanStep - return new JdbcTableScanStep(dslContext, - sqlDialect, - false, - plan(sqlDialect, tableScan.getSelectStatement(), aggregate)); + SelectStatement aggregation = pushdownAggregationOverScan( + jdbcReadStep.getSelectStatement(), + aggregate + ); + + return new JdbcReadStep(dslContext, + sqlDialect, + aggregation); } return super.visitAggregate(aggregate); @@ -74,18 +85,20 @@ public IPhysicalPlan visitTableScan(LogicalTableScan tableScan) { selectStatement.getWhere().and(tableScan.filter()); selectStatement.getSelectorList().addAll(tableScan.selectorList()); - return new JdbcTableScanStep( + return new JdbcReadStep( dslContext, sqlDialect, - false, selectStatement ); } - private SelectStatement plan(ISqlDialect sqlDialect, SelectStatement select, LogicalAggregate aggregate) { + private SelectStatement pushdownAggregationOverScan(SelectStatement select, LogicalAggregate aggregate) { SelectStatementBuilder builder = new SelectStatementBuilder(); - return builder.sqlDialect(sqlDialect) - .fields(List.of(new Selector(new ExpressionNode(StringUtils.format("%s(%s)", aggregate.func(), aggregate.field().getName())), + return builder.sqlDialect(this.sqlDialect) + .schema(this.schema) + .interval(this.interval) + .fields(List.of(new Selector(new ExpressionNode(this.schema, + StringUtils.format("%s(%s)", aggregate.func(), aggregate.field().getName())), aggregate.field().getName(), aggregate.field().getDataType()))) .filter(LogicalExpression.create(LogicalExpression.AND.OP, select.getWhere().getExpressions())) diff --git a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java index 1d0292ad84..235f68f6c7 100644 --- a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java +++ b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java @@ -21,15 +21,30 @@ import org.bithon.component.commons.expression.IdentifierExpression; import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.expression.LogicalExpression; +import org.bithon.server.commons.time.TimeSpan; +import org.bithon.server.datasource.DefaultSchema; +import org.bithon.server.datasource.ISchema; +import org.bithon.server.datasource.TimestampSpec; +import org.bithon.server.datasource.column.ExpressionColumn; +import org.bithon.server.datasource.column.StringColumn; +import org.bithon.server.datasource.column.aggregatable.last.AggregateLongLastColumn; +import org.bithon.server.datasource.column.aggregatable.sum.AggregateLongSumColumn; +import org.bithon.server.datasource.query.IDataSourceReader; +import org.bithon.server.datasource.query.Interval; import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; +import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.reader.clickhouse.AggregateFunctionColumn; import org.bithon.server.datasource.reader.h2.H2SqlDialect; +import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.statement.JdbcPhysicalPlanner; +import org.bithon.server.datasource.store.IDataStoreSpec; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.util.Arrays; import java.util.List; /** @@ -37,17 +52,67 @@ * @date 2025/6/5 20:58 */ public class JdbcPhysicalPlannerTest { + private final ISchema schema = new DefaultSchema("bithon-jvm-metrics", + "bithon-jvm-metrics", + new TimestampSpec("timestamp"), + Arrays.asList(new StringColumn("appName", "appName"), + new StringColumn("instance", "instance")), + Arrays.asList(new AggregateLongSumColumn("responseTime", "responseTime"), + new AggregateLongSumColumn("totalCount", "totalCount"), + new AggregateLongSumColumn("count4xx", "count4xx"), + new AggregateLongSumColumn("count5xx", "count5xx"), + new AggregateLongLastColumn("activeThreads", "activeThreads"), + new AggregateLongLastColumn("totalThreads", "totalThreads"), + new ExpressionColumn("avgResponseTime", + null, + "sum(responseTime) / sum(totalCount)", + "double"), + + // For ClickHouse data source + new AggregateFunctionColumn("clickedSum", "clickedSum", "sum", IDataType.LONG), + new AggregateFunctionColumn("clickedCnt", "clickedCnt", "count", IDataType.LONG) + ), + null, + new IDataStoreSpec() { + @Override + public String getStore() { + return "bithon_jvm_metrics"; + } + + @Override + public void setSchema(ISchema schema) { + + } + + @Override + public boolean isInternal() { + return false; + } + + @Override + public IDataSourceReader createReader() { + return null; + } + }, + null, + null); + + private ISqlDialect h2Dialect = new H2SqlDialect(); + @Test public void testTableScanPlan() { ILogicalPlan logicalPlan = new LogicalTableScan("test_table", List.of(new Selector("a1", "a1", IDataType.DOUBLE)), null); - IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, new H2SqlDialect()).plan(logicalPlan); + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) + .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), + TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), + logicalPlan); Assertions.assertEquals(""" - TableScan - SELECT "a1" AS "a1" - FROM "test_table" AS "test_table" - """, physicalPlan.serializeToText()); + JdbcReadStep + SELECT "a1" AS "a1" + FROM "test_table" AS "test_table" + """, physicalPlan.serializeToText()); } @Test @@ -55,12 +120,41 @@ public void testTableScanPlanWithFilter() { ILogicalPlan logicalPlan = new LogicalTableScan("test_table", List.of(new Selector("a1", "a1", IDataType.DOUBLE)), new LogicalExpression.AND(new ComparisonExpression.EQ(IdentifierExpression.of("a1"), LiteralExpression.ofString("jacky")))); - IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, new H2SqlDialect()).plan(logicalPlan); + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) + .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), + TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), + logicalPlan); + Assertions.assertEquals(""" + JdbcReadStep + SELECT "a1" AS "a1" + FROM "test_table" AS "test_table" + WHERE ("a1" = 'jacky') + """, physicalPlan.serializeToText()); + } + + @Test + public void testAggregatePlan() { + ILogicalPlan tableScan = new LogicalTableScan("bithon-jvm-metrics", + List.of(new Selector("totalCount", "totalCount", IDataType.DOUBLE)), + new LogicalExpression.AND(new ComparisonExpression.EQ(IdentifierExpression.of("a1"), LiteralExpression.ofString("jacky")))); + + ILogicalPlan aggregatePlan = new LogicalAggregate("sum", + tableScan, + new AggregateLongSumColumn("totalCount", "totalCount"), + List.of("appName"), + null); + + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, h2Dialect, this.schema) + .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), + TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), + aggregatePlan); Assertions.assertEquals(""" - TableScan - SELECT "a1" AS "a1" - FROM "test_table" AS "test_table" - WHERE ("a1" = 'jacky') - """, physicalPlan.serializeToText()); + JdbcReadStep + SELECT "appName", + sum("totalCount") AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."a1" = 'jacky') + GROUP BY "appName" + """, physicalPlan.serializeToText()); } } From a96265921d59e7fe528b6c150dba99b5a5f4ec75 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 11 Jun 2025 18:40:31 +0800 Subject: [PATCH 15/22] Add aggregate/arithmetic support to jdbc physical planner --- .../plan/logical/LogicalPlanSerializer.java | 2 +- .../query/plan/logical/LogicalScalar.java | 4 +- .../query/plan/physical/ArithmeticStep.java | 11 + .../query/plan/physical/IPhysicalPlan.java | 12 +- .../query/plan/physical/LiteralQueryStep.java | 5 + .../plan/physical/PhysicalPlanSerializer.java | 47 +++++ .../plan/physical}/LiteralQueryStepTest.java | 3 +- .../reader/jdbc/pipeline/JdbcReadStep.java | 9 +- .../jdbc/statement/JdbcPhysicalPlanner.java | 29 ++- .../expression/plan/LogicalPlanBuilder.java | 39 +++- .../builder/JdbcPhysicalPlannerTest.java | 190 ++++++++++++++---- 11 files changed, 296 insertions(+), 55 deletions(-) create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanSerializer.java rename server/{metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step => datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical}/LiteralQueryStepTest.java (95%) diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanSerializer.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanSerializer.java index df88f988dc..344319df13 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanSerializer.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalPlanSerializer.java @@ -70,7 +70,7 @@ public String visitBinaryOp(LogicalBinaryOp binaryOp) { @Override public String visitScalar(LogicalScalar scalar) { - return indent() + "Scalar(value=" + scalar.value() + ")"; + return indent() + "Scalar(value=" + scalar.literal().serializeToText() + ")"; } @Override diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java index bc0e5b2405..6795d4d72c 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java @@ -16,11 +16,13 @@ package org.bithon.server.datasource.query.plan.logical; +import org.bithon.component.commons.expression.LiteralExpression; + /** * @author frank.chen021@outlook.com * @date 2025/6/4 23:33 */ -public record LogicalScalar(double value) implements ILogicalPlan { +public record LogicalScalar(LiteralExpression literal) implements ILogicalPlan { @Override public T accept(ILogicalPlanVisitor visitor) { diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ArithmeticStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ArithmeticStep.java index 7b32b96516..de5a0647b9 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ArithmeticStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ArithmeticStep.java @@ -112,6 +112,17 @@ protected ArithmeticStep(IPhysicalPlan left, this.retainedColumns = retainedColumns == null ? Collections.emptySet() : new LinkedHashSet<>(Arrays.asList(retainedColumns)); } + @Override + public void serializer(PhysicalPlanSerializer serializer) { + serializer.append(this.getClass().getSimpleName()).append("Step") + .append(", Result Column: ").append(resultColumnName) + .append(", Retained Columns: ").append(retainedColumns.toString()).append("\n") + .append(" lhs: \n") + .append(" ", this.lhs.serializeToText()) + .append(" rhs: \n") + .append(" ", this.rhs.serializeToText()); + } + @Override public CompletableFuture execute() throws Exception { CompletableFuture leftFuture = this.lhs.execute(); diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java index 14276144c7..cba8c6091d 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java @@ -30,14 +30,14 @@ public interface IPhysicalPlan { boolean isScalar(); default String serializeToText() { - StringBuilder builder = new StringBuilder(); - serializer(builder); - return builder.toString(); + PhysicalPlanSerializer serializer = new PhysicalPlanSerializer(); + serializer(serializer); + return serializer.getSerializedPlan(); } - default void serializer(StringBuilder builder) { - builder.append(this.getClass().getSimpleName()); - builder.append("\n"); + default void serializer(PhysicalPlanSerializer serializer) { + serializer.append(this.getClass().getSimpleName()); + serializer.append("\n"); } CompletableFuture execute() throws Exception; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LiteralQueryStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LiteralQueryStep.java index 3be02f7a94..9fc1c382c5 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LiteralQueryStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/LiteralQueryStep.java @@ -59,6 +59,11 @@ public LiteralQueryStep(LiteralExpression expression, Interval interval) { } } + @Override + public void serializer(PhysicalPlanSerializer serializer) { + serializer.append(expression.serializeToText()); + } + @Override public boolean isScalar() { return size == 1; diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanSerializer.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanSerializer.java new file mode 100644 index 0000000000..58d2c50843 --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanSerializer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.physical; + + +/** + * @author frank.chen021@outlook.com + * @date 11/6/25 4:20 pm + */ +public class PhysicalPlanSerializer { + private final StringBuilder builder = new StringBuilder(128); + + public PhysicalPlanSerializer append(String text) { + builder.append(text); + return this; + } + + public PhysicalPlanSerializer append(String indent, String text) { + for (String line : text.split("\\r?\\n")) { + builder.append(indent).append(line).append("\n"); + } + return this; + } + + public String getSerializedPlan() { + return builder.toString(); + } + + public PhysicalPlanSerializer append(char c) { + builder.append(c); + return this; + } +} diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStepTest.java b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/LiteralQueryStepTest.java similarity index 95% rename from server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStepTest.java rename to server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/LiteralQueryStepTest.java index 74c7748d0f..939dd10b81 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/LiteralQueryStepTest.java +++ b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/LiteralQueryStepTest.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package org.bithon.server.metric.expression.pipeline.step; +package org.bithon.server.datasource.query.plan.physical; import org.bithon.component.commons.expression.IDataType; import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.plan.physical.LiteralQueryStep; import org.bithon.server.datasource.query.result.PipelineQueryResult; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java index 942f82d370..f85029ceb9 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java @@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.query.plan.physical.PhysicalPlanSerializer; import org.bithon.server.datasource.query.result.Column; import org.bithon.server.datasource.query.result.ColumnarTable; import org.bithon.server.datasource.query.result.PipelineQueryResult; @@ -55,12 +56,10 @@ public JdbcReadStep(DSLContext dslContext, } @Override - public void serializer(StringBuilder builder) { + public void serializer(PhysicalPlanSerializer serializer) { String stepName = this.getClass().getSimpleName(); - builder.append(stepName).append('\n'); - for (String line : this.sql.split("\\r?\\n")) { - builder.append(" ").append(line).append("\n"); - } + serializer.append(stepName).append('\n'); + serializer.append(" ", this.sql); } @Override diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java index 010ebb2240..31e81bdf7c 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java @@ -16,6 +16,7 @@ package org.bithon.server.datasource.reader.jdbc.statement; +import org.bithon.component.commons.expression.ComparisonExpression; import org.bithon.component.commons.expression.LogicalExpression; import org.bithon.component.commons.utils.StringUtils; import org.bithon.server.datasource.ISchema; @@ -24,8 +25,12 @@ import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; +import org.bithon.server.datasource.query.plan.logical.LogicalBinaryOp; +import org.bithon.server.datasource.query.plan.logical.LogicalScalar; import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; +import org.bithon.server.datasource.query.plan.physical.ArithmeticStep; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.query.plan.physical.LiteralQueryStep; import org.bithon.server.datasource.query.plan.physical.PhysicalPlanner; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.pipeline.JdbcReadStep; @@ -82,7 +87,10 @@ public IPhysicalPlan visitTableScan(LogicalTableScan tableScan) { SelectStatement selectStatement = new SelectStatement(); selectStatement.getFrom().setExpression(new TableIdentifier(tableScan.table())); selectStatement.getFrom().setAlias(tableScan.table()); - selectStatement.getWhere().and(tableScan.filter()); + selectStatement.getWhere().and( + new ComparisonExpression.GTE(this.interval.getTimestampColumn(), sqlDialect.toISO8601TimestampExpression(this.interval.getStartTime())), + new ComparisonExpression.LT(this.interval.getTimestampColumn(), sqlDialect.toISO8601TimestampExpression(this.interval.getEndTime())), + tableScan.filter()); selectStatement.getSelectorList().addAll(tableScan.selectorList()); return new JdbcReadStep( @@ -92,6 +100,25 @@ public IPhysicalPlan visitTableScan(LogicalTableScan tableScan) { ); } + @Override + public IPhysicalPlan visitBinaryOp(LogicalBinaryOp binaryOp) { + return switch (binaryOp.op()) { + case ADD -> new ArithmeticStep.Add(binaryOp.left().accept(this), + binaryOp.right().accept(this)); + case SUB -> new ArithmeticStep.Sub(binaryOp.left().accept(this), + binaryOp.right().accept(this)); + case MUL -> new ArithmeticStep.Mul(binaryOp.left().accept(this), + binaryOp.right().accept(this)); + case DIV -> new ArithmeticStep.Div(binaryOp.left().accept(this), + binaryOp.right().accept(this)); + }; + } + + @Override + public IPhysicalPlan visitScalar(LogicalScalar scalar) { + return new LiteralQueryStep(scalar.literal()); + } + private SelectStatement pushdownAggregationOverScan(SelectStatement select, LogicalAggregate aggregate) { SelectStatementBuilder builder = new SelectStatementBuilder(); return builder.sqlDialect(this.sqlDialect) diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java index a0cdcff059..11f1b46177 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java @@ -16,10 +16,17 @@ package org.bithon.server.metric.expression.plan; +import org.bithon.component.commons.expression.ArithmeticExpression; +import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.utils.CollectionUtils; +import org.bithon.server.datasource.ISchema; +import org.bithon.server.datasource.column.IColumn; import org.bithon.server.datasource.query.ast.Selector; +import org.bithon.server.datasource.query.plan.logical.BinaryOp; import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; +import org.bithon.server.datasource.query.plan.logical.LogicalBinaryOp; +import org.bithon.server.datasource.query.plan.logical.LogicalScalar; import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; import org.bithon.server.metric.expression.ast.IMetricExpressionVisitor; import org.bithon.server.metric.expression.ast.MetricAggregateExpression; @@ -32,7 +39,13 @@ * @author frank.chen021@outlook.com * @date 2025/6/4 23:43 */ -class LogicalPlanBuilder implements IMetricExpressionVisitor { +public class LogicalPlanBuilder implements IMetricExpressionVisitor { + private final ISchema schema; + + public LogicalPlanBuilder(ISchema schema) { + this.schema = schema; + } + @Override public ILogicalPlan visit(MetricSelectExpression expression) { return new LogicalTableScan( @@ -44,6 +57,7 @@ public ILogicalPlan visit(MetricSelectExpression expression) { @Override public ILogicalPlan visit(MetricAggregateExpression expression) { + IColumn col = schema.getColumnByName(expression.getMetric().getName()); return new LogicalAggregate( expression.getMetric().getAggregator(), new LogicalTableScan(expression.getFrom(), @@ -51,9 +65,30 @@ public ILogicalPlan visit(MetricAggregateExpression expression) { expression.getMetric().getName(), expression.getDataType())), expression.getLabelSelectorExpression()), - null, + col, CollectionUtils.isEmpty(expression.getGroupBy()) ? new ArrayList<>() : new ArrayList<>(expression.getGroupBy()), new ArrayList<>() ); } + + @Override + public ILogicalPlan visit(ArithmeticExpression expression) { + BinaryOp op = switch (expression.getOperator()) { + case ADD -> BinaryOp.ADD; + case SUB -> BinaryOp.SUB; + case MUL -> BinaryOp.MUL; + case DIV -> BinaryOp.DIV; + }; + + return new LogicalBinaryOp( + expression.getLhs().accept(this), + op, + expression.getRhs().accept(this) + ); + } + + @Override + public ILogicalPlan visit(LiteralExpression expression) { + return new LogicalScalar(expression); + } } diff --git a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java index 235f68f6c7..a7dd1eaa29 100644 --- a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java +++ b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java @@ -16,11 +16,9 @@ package org.bithon.server.storage.jdbc.common.statement.builder; -import org.bithon.component.commons.expression.ComparisonExpression; import org.bithon.component.commons.expression.IDataType; +import org.bithon.component.commons.expression.IExpression; import org.bithon.component.commons.expression.IdentifierExpression; -import org.bithon.component.commons.expression.LiteralExpression; -import org.bithon.component.commons.expression.LogicalExpression; import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.DefaultSchema; import org.bithon.server.datasource.ISchema; @@ -31,21 +29,22 @@ import org.bithon.server.datasource.column.aggregatable.sum.AggregateLongSumColumn; import org.bithon.server.datasource.query.IDataSourceReader; import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; -import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; -import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.query.setting.QuerySettings; import org.bithon.server.datasource.reader.clickhouse.AggregateFunctionColumn; import org.bithon.server.datasource.reader.h2.H2SqlDialect; +import org.bithon.server.datasource.reader.jdbc.JdbcDataSourceReader; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.statement.JdbcPhysicalPlanner; import org.bithon.server.datasource.store.IDataStoreSpec; +import org.bithon.server.metric.expression.ast.MetricExpressionASTBuilder; +import org.bithon.server.metric.expression.plan.LogicalPlanBuilder; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.time.Duration; import java.util.Arrays; -import java.util.List; /** * @author frank.chen021@outlook.com @@ -91,70 +90,187 @@ public boolean isInternal() { @Override public IDataSourceReader createReader() { - return null; + return new JdbcDataSourceReader(null, new H2SqlDialect(), QuerySettings.DEFAULT); } }, null, null); - private ISqlDialect h2Dialect = new H2SqlDialect(); + private final ISqlDialect h2Dialect = new H2SqlDialect(); @Test public void testTableScanPlan() { - ILogicalPlan logicalPlan = new LogicalTableScan("test_table", - List.of(new Selector("a1", "a1", IDataType.DOUBLE)), - null); + IExpression ast = MetricExpressionASTBuilder.parse("bithon-jvm-metrics.totalCount"); + ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), logicalPlan); + Assertions.assertEquals(""" JdbcReadStep - SELECT "a1" AS "a1" - FROM "test_table" AS "test_table" - """, physicalPlan.serializeToText()); + SELECT "totalCount" AS "totalCount" + FROM "bithon-jvm-metrics" AS "bithon-jvm-metrics" + WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') + """, + physicalPlan.serializeToText()); } @Test public void testTableScanPlanWithFilter() { - ILogicalPlan logicalPlan = new LogicalTableScan("test_table", - List.of(new Selector("a1", "a1", IDataType.DOUBLE)), - new LogicalExpression.AND(new ComparisonExpression.EQ(IdentifierExpression.of("a1"), LiteralExpression.ofString("jacky")))); + IExpression ast = MetricExpressionASTBuilder.parse("bithon-jvm-metrics.totalCount{appName=\"jacky\"}"); + ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), logicalPlan); + Assertions.assertEquals(""" JdbcReadStep - SELECT "a1" AS "a1" - FROM "test_table" AS "test_table" - WHERE ("a1" = 'jacky') - """, physicalPlan.serializeToText()); + SELECT "totalCount" AS "totalCount" + FROM "bithon-jvm-metrics" AS "bithon-jvm-metrics" + WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("appName" = 'jacky') + """, + physicalPlan.serializeToText()); } @Test - public void testAggregatePlan() { - ILogicalPlan tableScan = new LogicalTableScan("bithon-jvm-metrics", - List.of(new Selector("totalCount", "totalCount", IDataType.DOUBLE)), - new LogicalExpression.AND(new ComparisonExpression.EQ(IdentifierExpression.of("a1"), LiteralExpression.ofString("jacky")))); - - ILogicalPlan aggregatePlan = new LogicalAggregate("sum", - tableScan, - new AggregateLongSumColumn("totalCount", "totalCount"), - List.of("appName"), - null); - - IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, h2Dialect, this.schema) + public void test_SumAggregate() { + IExpression ast = MetricExpressionASTBuilder.parse("sum(bithon-jvm-metrics.totalCount{appName=\"jacky\"})"); + ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), - aggregatePlan); + logicalPlan); + + // TODO: Remove duplicate timestamp filter + Assertions.assertEquals(""" + JdbcReadStep + SELECT sum("totalCount") AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_SumAggregate_GroupBy() { + IExpression ast = MetricExpressionASTBuilder.parse("sum(bithon-jvm-metrics.totalCount{appName=\"jacky\"}) by (appName)"); + ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) + .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), + TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), + logicalPlan); + + // TODO: Remove duplicate timestamp filter Assertions.assertEquals(""" JdbcReadStep SELECT "appName", sum("totalCount") AS "totalCount" FROM "bithon_jvm_metrics" - WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."a1" = 'jacky') + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) GROUP BY "appName" - """, physicalPlan.serializeToText()); + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_SumAggregate_GroupBy_TimeSeries() { + IExpression ast = MetricExpressionASTBuilder.parse("sum(bithon-jvm-metrics.totalCount{appName=\"jacky\"}) by (appName)"); + ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) + .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), + TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800"), + Duration.ofSeconds(10), + new IdentifierExpression("timestamp")), + logicalPlan); + + // TODO: Remove duplicate timestamp filter + Assertions.assertEquals(""" + JdbcReadStep + SELECT UNIX_TIMESTAMP("timestamp")/ 10 * 10 AS "_timestamp", + "appName", + sum("totalCount") AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + GROUP BY "appName", "_timestamp" + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_LastAggregate() { + IExpression ast = MetricExpressionASTBuilder.parse("last(bithon-jvm-metrics.totalCount{appName=\"jacky\"}) by (appName)"); + ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) + .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), + TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), + logicalPlan); + + // TODO: Remove duplicate timestamp filter + Assertions.assertEquals(""" + JdbcReadStep + SELECT "appName", + "totalCount" + FROM + ( + SELECT "appName", + FIRST_VALUE("totalCount") OVER (PARTITION BY (UNIX_TIMESTAMP("timestamp") / 600) * 600 ORDER BY "timestamp" ASC) AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + ) + GROUP BY "appName", "totalCount" + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_Arithmetic_TwoExpressions() { + IExpression ast = MetricExpressionASTBuilder.parse("sum(bithon-jvm-metrics.count4xx{appName=\"jacky\"}) + sum(bithon-jvm-metrics.count5xx{appName=\"jacky\"})"); + ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) + .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), + TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), + logicalPlan); + + // TODO: Remove duplicate timestamp filter + Assertions.assertEquals(""" + AddStep, Result Column: value, Retained Columns: [] + lhs:\s + JdbcReadStep + SELECT sum("count4xx") AS "count4xx" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + rhs:\s + JdbcReadStep + SELECT sum("count5xx") AS "count5xx" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_Arithmetic_WithLiteral() { + IExpression ast = MetricExpressionASTBuilder.parse("sum(bithon-jvm-metrics.count4xx{appName=\"jacky\"}) + 5"); + ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); + IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) + .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), + TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), + logicalPlan); + + // TODO: Remove duplicate timestamp filter + Assertions.assertEquals(""" + AddStep, Result Column: value, Retained Columns: [] + lhs:\s + JdbcReadStep + SELECT sum("count4xx") AS "count4xx" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + rhs:\s + 5 + """, + physicalPlan.serializeToText()); } } From 40de16fa1f28884c11fbb13f22a978f27e0589e9 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 11 Jun 2025 18:51:38 +0800 Subject: [PATCH 16/22] Deduplicate expressions --- .../commons/expression/BinaryExpression.java | 14 ++++++++ .../expression/IdentifierExpression.java | 16 +++++++++ .../commons/expression/LiteralExpression.java | 24 +++++++++---- .../jdbc/statement/ast/WhereClause.java | 34 +++++++++++++++---- .../builder/JdbcPhysicalPlannerTest.java | 16 ++++----- 5 files changed, 83 insertions(+), 21 deletions(-) diff --git a/component/component-commons/src/main/java/org/bithon/component/commons/expression/BinaryExpression.java b/component/component-commons/src/main/java/org/bithon/component/commons/expression/BinaryExpression.java index 0752d83076..44c5913301 100644 --- a/component/component-commons/src/main/java/org/bithon/component/commons/expression/BinaryExpression.java +++ b/component/component-commons/src/main/java/org/bithon/component/commons/expression/BinaryExpression.java @@ -62,4 +62,18 @@ public String getType() { public void serializeToText(ExpressionSerializer serializer) { serializer.serialize(this); } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof BinaryExpression)) { + return false; + } + BinaryExpression that = (BinaryExpression) obj; + return this.type.equals(that.type) && + this.lhs.equals(that.lhs) && + this.rhs.equals(that.rhs); + } } diff --git a/component/component-commons/src/main/java/org/bithon/component/commons/expression/IdentifierExpression.java b/component/component-commons/src/main/java/org/bithon/component/commons/expression/IdentifierExpression.java index 746a3e1056..e5cdd79e57 100644 --- a/component/component-commons/src/main/java/org/bithon/component/commons/expression/IdentifierExpression.java +++ b/component/component-commons/src/main/java/org/bithon/component/commons/expression/IdentifierExpression.java @@ -115,4 +115,20 @@ public Object evaluate(IEvaluationContext context) { public String toString() { return identifier; } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof IdentifierExpression)) { + return false; + } + IdentifierExpression that = (IdentifierExpression) obj; + return this.identifier.equals(that.identifier) && + ((this.qualifier == null && that.qualifier == null) || + (this.qualifier != null && this.qualifier.equals(that.qualifier))) && + ((this.dataType == null && that.dataType == null) || + (this.dataType != null && this.dataType.equals(that.dataType))); + } } diff --git a/component/component-commons/src/main/java/org/bithon/component/commons/expression/LiteralExpression.java b/component/component-commons/src/main/java/org/bithon/component/commons/expression/LiteralExpression.java index 65fe956bbf..fb423469a8 100644 --- a/component/component-commons/src/main/java/org/bithon/component/commons/expression/LiteralExpression.java +++ b/component/component-commons/src/main/java/org/bithon/component/commons/expression/LiteralExpression.java @@ -111,7 +111,7 @@ protected LiteralExpression(T value) { } /** - * NOTE: do not change the method name since it's used in the ExpressionDeserializer} + * NOTE: do not change the method name since it's used in the ExpressionDeserializer */ public T getValue() { return value; @@ -162,6 +162,18 @@ public void serializeToText(ExpressionSerializer serializer) { serializer.serialize(this); } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof LiteralExpression)) { + return false; + } + LiteralExpression that = (LiteralExpression) obj; + return this.getDataType() == that.getDataType() && this.value.equals(that.value); + } + public abstract boolean canNegate(); public LiteralExpression negate() { @@ -244,7 +256,7 @@ public LiteralExpression castTo(IDataType targetType) { return this; case DOUBLE: - return new LiteralExpression.DoubleLiteral(((Number) value).doubleValue()); + return new LiteralExpression.DoubleLiteral(value.doubleValue()); case BOOLEAN: return new LiteralExpression.BooleanLiteral(value != 0); @@ -291,13 +303,13 @@ public LiteralExpression castTo(IDataType targetType) { return new LiteralExpression.StringLiteral(value.toString()); case LONG: - return new LiteralExpression.LongLiteral(((Number) value).longValue()); + return new LiteralExpression.LongLiteral(value.longValue()); case DOUBLE: return this; case BOOLEAN: - return new LiteralExpression.BooleanLiteral(((Number) value).doubleValue() != 0); + return new LiteralExpression.BooleanLiteral(value != 0); default: throw new UnsupportedOperationException("Can't cast a boolean value into type of " + targetType); @@ -336,13 +348,13 @@ public LiteralExpression castTo(IDataType targetType) { return new LiteralExpression.StringLiteral(value.toString()); case LONG: - return new LiteralExpression.LongLiteral(((Number) value).longValue()); + return new LiteralExpression.LongLiteral(value.longValue()); case DOUBLE: return new LiteralExpression.DoubleLiteral(value.doubleValue()); case BOOLEAN: - return new LiteralExpression.BooleanLiteral(((Number) value).doubleValue() != 0); + return new LiteralExpression.BooleanLiteral(value.doubleValue() != 0); default: throw new UnsupportedOperationException("Can't cast a boolean value into type of " + targetType); diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/ast/WhereClause.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/ast/WhereClause.java index 5a76955e8e..3a6ae394e1 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/ast/WhereClause.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/ast/WhereClause.java @@ -18,6 +18,7 @@ import lombok.Getter; import org.bithon.component.commons.expression.IExpression; +import org.bithon.component.commons.expression.LogicalExpression; import org.bithon.server.datasource.query.ast.IASTNode; import java.util.ArrayList; @@ -33,23 +34,44 @@ public class WhereClause implements IASTNode { private final List expressions = new ArrayList<>(); public WhereClause and(IExpression expression) { - if (expression != null) { + if (expression == null) { + return this; + } + + if (expression instanceof LogicalExpression.AND andExpression) { + // Flatten the AND expression + for (IExpression expr : andExpression.getOperands()) { + addExpression(expr); + } + } else { expressions.add(expression); } return this; } public WhereClause and(IExpression... expressions) { - if (expressions != null) { - for (IExpression expression : expressions) { - if (expression != null) { - this.expressions.add(expression); - } + if (expressions == null) { + return this; + } + + for (IExpression expression : expressions) { + if (expression == null) { + continue; } + + addExpression(expression); } return this; } + private void addExpression(IExpression expr) { + // Check if the expression already exists in the list + if (this.expressions.stream().anyMatch(e -> e.equals(expr))) { + return; + } + this.expressions.add(expr); + } + public boolean isEmpty() { return expressions.isEmpty(); } diff --git a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java index a7dd1eaa29..ba0bc89262 100644 --- a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java +++ b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java @@ -149,7 +149,7 @@ public void test_SumAggregate() { JdbcReadStep SELECT sum("totalCount") AS "totalCount" FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') """, physicalPlan.serializeToText()); } @@ -169,7 +169,7 @@ public void test_SumAggregate_GroupBy() { SELECT "appName", sum("totalCount") AS "totalCount" FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') GROUP BY "appName" """, physicalPlan.serializeToText()); @@ -193,7 +193,7 @@ SELECT UNIX_TIMESTAMP("timestamp")/ 10 * 10 AS "_timestamp", "appName", sum("totalCount") AS "totalCount" FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') GROUP BY "appName", "_timestamp" """, physicalPlan.serializeToText()); @@ -218,7 +218,7 @@ public void test_LastAggregate() { SELECT "appName", FIRST_VALUE("totalCount") OVER (PARTITION BY (UNIX_TIMESTAMP("timestamp") / 600) * 600 ORDER BY "timestamp" ASC) AS "totalCount" FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') ) GROUP BY "appName", "totalCount" """, @@ -234,19 +234,18 @@ public void test_Arithmetic_TwoExpressions() { TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), logicalPlan); - // TODO: Remove duplicate timestamp filter Assertions.assertEquals(""" AddStep, Result Column: value, Retained Columns: [] lhs:\s JdbcReadStep SELECT sum("count4xx") AS "count4xx" FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') rhs:\s JdbcReadStep SELECT sum("count5xx") AS "count5xx" FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') """, physicalPlan.serializeToText()); } @@ -260,14 +259,13 @@ public void test_Arithmetic_WithLiteral() { TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), logicalPlan); - // TODO: Remove duplicate timestamp filter Assertions.assertEquals(""" AddStep, Result Column: value, Retained Columns: [] lhs:\s JdbcReadStep SELECT sum("count4xx") AS "count4xx" FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND (("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky')) + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') rhs:\s 5 """, From eb3fcb617476ead1a34a28f02f9d2e1660882db3 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 11 Jun 2025 22:11:07 +0800 Subject: [PATCH 17/22] uplift JdbcPhysicalPlanner --- .../server/datasource/ISchemaProvider.java | 25 + .../datasource/query/IDataSourceReader.java | 5 +- .../query/plan/logical/LogicalTableScan.java | 3 +- .../query/plan/physical/IPhysicalPlan.java | 10 + .../query/plan/physical/PhysicalPlanner.java | 61 -- .../reader/jdbc/JdbcDataSourceReader.java | 25 +- .../jdbc/pipeline/JdbcPipelineBuilder.java | 12 +- .../reader/jdbc/pipeline/JdbcReadStep.java | 39 +- .../jdbc/statement/JdbcPhysicalPlanner.java | 135 ---- .../metric/expression/api/MetricQueryApi.java | 2 +- .../expression/pipeline/PhysicalPlanner.java | 100 ++- .../expression/plan/LogicalPlanBuilder.java | 18 +- .../pipeline/step/ArithmeticStepTest.java | 720 +++++++++--------- .../builder/JdbcPhysicalPlannerTest.java | 274 ------- .../builder/PhysicalPlannerTest.java | 301 ++++++++ .../storage/datasource/SchemaManager.java | 3 +- 16 files changed, 883 insertions(+), 850 deletions(-) create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/ISchemaProvider.java delete mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanner.java delete mode 100644 server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java delete mode 100644 server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java create mode 100644 server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/ISchemaProvider.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/ISchemaProvider.java new file mode 100644 index 0000000000..445c83870b --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/ISchemaProvider.java @@ -0,0 +1,25 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/11 20:34 + */ +public interface ISchemaProvider { + ISchema getSchema(String name) throws SchemaException; +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java index 865f72f8f8..7235be8db7 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/IDataSourceReader.java @@ -17,8 +17,7 @@ package org.bithon.server.datasource.query; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import org.bithon.server.datasource.ISchema; -import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; +import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.result.ColumnarTable; @@ -31,7 +30,7 @@ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public interface IDataSourceReader extends AutoCloseable { - default IPhysicalPlan plan(ISchema schema, Interval interval, ILogicalPlan plan) { + default IPhysicalPlan plan(LogicalTableScan tableScan, Interval interval) { throw new UnsupportedOperationException("This data source does not support logical plan execution"); } diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java index 3cc7557bb5..2b55de97a9 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java @@ -17,6 +17,7 @@ package org.bithon.server.datasource.query.plan.logical; import org.bithon.component.commons.expression.IExpression; +import org.bithon.server.datasource.ISchema; import org.bithon.server.datasource.query.ast.Selector; import java.util.List; @@ -26,7 +27,7 @@ * @date 2025/6/4 23:31 */ public record LogicalTableScan( - String table, + ISchema table, List selectorList, IExpression filter // = filters like {job="abc"} ) implements ILogicalPlan { diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java index cba8c6091d..9d65794c54 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java @@ -17,6 +17,7 @@ package org.bithon.server.datasource.query.plan.physical; +import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; import org.bithon.server.datasource.query.result.PipelineQueryResult; import java.util.concurrent.CompletableFuture; @@ -27,6 +28,15 @@ */ public interface IPhysicalPlan { + default boolean canPushDownAggregate(LogicalAggregate aggregate) { + return false; + } + + default IPhysicalPlan pushDownAggregate(LogicalAggregate aggregate) { + throw new UnsupportedOperationException("Cannot push down aggregate: " + aggregate); + } + + boolean isScalar(); default String serializeToText() { diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanner.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanner.java deleted file mode 100644 index 6fa606e320..0000000000 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/PhysicalPlanner.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2020 bithon.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bithon.server.datasource.query.plan.physical; - - -import org.bithon.server.datasource.query.plan.logical.ILogicalPlanVisitor; -import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; -import org.bithon.server.datasource.query.plan.logical.LogicalBinaryOp; -import org.bithon.server.datasource.query.plan.logical.LogicalFilter; -import org.bithon.server.datasource.query.plan.logical.LogicalScalar; -import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; - -/** - * @author frank.chen021@outlook.com - * @date 4/4/25 3:53 pm - */ -public class PhysicalPlanner implements ILogicalPlanVisitor { - - - @Override - public IPhysicalPlan visitTableScan(LogicalTableScan tableScan) { - return null; - } - - @Override - public IPhysicalPlan visitAggregate(LogicalAggregate aggregate) { - return null; - } - - @Override - public IPhysicalPlan visitBinaryOp(LogicalBinaryOp binaryOp) { - // Implementation for visiting binary operations - return null; // Placeholder - } - - @Override - public IPhysicalPlan visitFilter(LogicalFilter filter) { - // Implementation for visiting filters - return null; // Placeholder - } - - @Override - public IPhysicalPlan visitScalar(LogicalScalar scalar) { - // Implementation for visiting scalars - return null; // Placeholder - } -} diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java index a934feb36a..fc1e29498a 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java @@ -24,7 +24,6 @@ import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.utils.Preconditions; import org.bithon.component.commons.utils.StringUtils; -import org.bithon.server.datasource.ISchema; import org.bithon.server.datasource.query.IDataSourceReader; import org.bithon.server.datasource.query.Interval; import org.bithon.server.datasource.query.Limit; @@ -32,13 +31,13 @@ import org.bithon.server.datasource.query.OrderBy; import org.bithon.server.datasource.query.Query; import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; +import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.result.ColumnarTable; import org.bithon.server.datasource.query.setting.QuerySettings; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.pipeline.JdbcPipelineBuilder; -import org.bithon.server.datasource.reader.jdbc.statement.JdbcPhysicalPlanner; +import org.bithon.server.datasource.reader.jdbc.pipeline.JdbcReadStep; import org.bithon.server.datasource.reader.jdbc.statement.ast.LimitClause; import org.bithon.server.datasource.reader.jdbc.statement.ast.OrderByClause; import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; @@ -118,8 +117,23 @@ public JdbcDataSourceReader(DSLContext dslContext, ISqlDialect sqlDialect, Query } @Override - public IPhysicalPlan plan(ISchema schema, Interval interval, ILogicalPlan plan) { - return new JdbcPhysicalPlanner(this.dslContext, this.sqlDialect, schema).plan(interval, plan); + public IPhysicalPlan plan(LogicalTableScan tableScan, Interval interval) { + SelectStatement selectStatement = new SelectStatement(); + selectStatement.getFrom().setExpression(new TableIdentifier(tableScan.table().getName())); + selectStatement.getFrom().setAlias(tableScan.table().getName()); + selectStatement.getWhere().and( + new ComparisonExpression.GTE(interval.getTimestampColumn(), sqlDialect.toISO8601TimestampExpression(interval.getStartTime())), + new ComparisonExpression.LT(interval.getTimestampColumn(), sqlDialect.toISO8601TimestampExpression(interval.getEndTime())), + tableScan.filter()); + selectStatement.getSelectorList().addAll(tableScan.selectorList()); + + return new JdbcReadStep( + dslContext, + tableScan.table(), + sqlDialect, + selectStatement, + interval + ); } @Override @@ -141,6 +155,7 @@ public ColumnarTable timeseries(Query query) { IPhysicalPlan queryStep = JdbcPipelineBuilder.builder() .dslContext(dslContext) .dialect(this.sqlDialect) + .schema(query.getSchema()) .selectStatement(selectStatement) .interval(Interval.of(interval.getStartTime().floor(query.getInterval().getStep()), interval.getEndTime(), diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java index ddadff1e7d..65c1e539ba 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java @@ -20,6 +20,7 @@ import org.bithon.component.commons.expression.IdentifierExpression; import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.utils.CollectionUtils; +import org.bithon.server.datasource.ISchema; import org.bithon.server.datasource.query.Interval; import org.bithon.server.datasource.query.Order; import org.bithon.server.datasource.query.ast.ExpressionNode; @@ -46,6 +47,7 @@ public class JdbcPipelineBuilder { private ISqlDialect dialect; private SelectStatement selectStatement; private Interval interval; + private ISchema schema; private JdbcPipelineBuilder() { } @@ -69,6 +71,10 @@ public JdbcPipelineBuilder selectStatement(SelectStatement selectStatement) { return this; } + public JdbcPipelineBuilder schema(ISchema schema) { + this.schema = schema; + return this; + } public JdbcPipelineBuilder interval(Interval interval) { this.interval = interval; @@ -93,7 +99,7 @@ public IPhysicalPlan build() { } if (windowFunctionSelectors.isEmpty()) { - return new JdbcReadStep(dslContext, dialect, selectStatement); + return new JdbcReadStep(dslContext, schema, dialect, selectStatement, this.interval); } WindowFunctionExpression windowFunctionExpression = (WindowFunctionExpression) ((ExpressionNode) windowFunctionSelectors.get(0).getSelectExpression()).getParsedExpression(); @@ -117,8 +123,10 @@ public IPhysicalPlan build() { .toArray(new OrderByClause[0])); IPhysicalPlan readStep = new JdbcReadStep(dslContext, + schema, dialect, - subQuery + subQuery, + interval ); return new SlidingWindowAggregationStep(orderBy.getIdentifier(), diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java index f85029ceb9..b4f176a227 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java @@ -19,7 +19,13 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.bithon.component.commons.expression.LogicalExpression; +import org.bithon.component.commons.utils.StringUtils; +import org.bithon.server.datasource.ISchema; +import org.bithon.server.datasource.query.Interval; +import org.bithon.server.datasource.query.ast.ExpressionNode; import org.bithon.server.datasource.query.ast.Selector; +import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.plan.physical.PhysicalPlanSerializer; import org.bithon.server.datasource.query.result.Column; @@ -27,6 +33,7 @@ import org.bithon.server.datasource.query.result.PipelineQueryResult; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; +import org.bithon.server.datasource.reader.jdbc.statement.builder.SelectStatementBuilder; import org.jooq.Cursor; import org.jooq.DSLContext; import org.jooq.Record; @@ -46,12 +53,21 @@ public class JdbcReadStep implements IPhysicalPlan { @Getter private final SelectStatement selectStatement; + private final ISqlDialect sqlDialect; + private final ISchema schema; + private final Interval interval; public JdbcReadStep(DSLContext dslContext, + ISchema schema, ISqlDialect sqlDialect, - SelectStatement selectStatement) { + SelectStatement selectStatement, + Interval interval + ) { this.dslContext = dslContext; + this.schema = schema; this.selectStatement = selectStatement; + this.interval = interval; + this.sqlDialect = sqlDialect; this.sql = selectStatement.toSQL(sqlDialect); } @@ -72,6 +88,27 @@ public boolean isScalar() { return false; } + @Override + public boolean canPushDownAggregate(LogicalAggregate aggregate) { + return true; + } + + @Override + public IPhysicalPlan pushDownAggregate(LogicalAggregate aggregate) { + SelectStatementBuilder builder = new SelectStatementBuilder(); + SelectStatement select = builder.sqlDialect(this.sqlDialect) + .schema(this.schema) + .interval(this.interval) + .fields(List.of(new Selector(new ExpressionNode(this.schema, + StringUtils.format("%s(%s)", aggregate.func(), aggregate.field().getName())), + aggregate.field().getName(), + aggregate.field().getDataType()))) + .filter(LogicalExpression.create(LogicalExpression.AND.OP, selectStatement.getWhere().getExpressions())) + .groupBy(aggregate.groupBy()) + .build(); + return new JdbcReadStep(dslContext, schema, sqlDialect, select, interval); + } + @Override public CompletableFuture execute() throws Exception { return CompletableFuture.supplyAsync(() -> { diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java deleted file mode 100644 index 31e81bdf7c..0000000000 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/statement/JdbcPhysicalPlanner.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2020 bithon.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bithon.server.datasource.reader.jdbc.statement; - -import org.bithon.component.commons.expression.ComparisonExpression; -import org.bithon.component.commons.expression.LogicalExpression; -import org.bithon.component.commons.utils.StringUtils; -import org.bithon.server.datasource.ISchema; -import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.ast.ExpressionNode; -import org.bithon.server.datasource.query.ast.Selector; -import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; -import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; -import org.bithon.server.datasource.query.plan.logical.LogicalBinaryOp; -import org.bithon.server.datasource.query.plan.logical.LogicalScalar; -import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; -import org.bithon.server.datasource.query.plan.physical.ArithmeticStep; -import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.LiteralQueryStep; -import org.bithon.server.datasource.query.plan.physical.PhysicalPlanner; -import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; -import org.bithon.server.datasource.reader.jdbc.pipeline.JdbcReadStep; -import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; -import org.bithon.server.datasource.reader.jdbc.statement.ast.TableIdentifier; -import org.bithon.server.datasource.reader.jdbc.statement.builder.SelectStatementBuilder; -import org.jooq.DSLContext; - -import java.util.List; - -/** - * @author frank.chen021@outlook.com - * @date 2025/6/5 20:14 - */ -public class JdbcPhysicalPlanner extends PhysicalPlanner { - private final DSLContext dslContext; - private final ISqlDialect sqlDialect; - private final ISchema schema; - private Interval interval; - - public JdbcPhysicalPlanner(DSLContext dslContext, - ISqlDialect sqlDialect, - ISchema schema) { - this.dslContext = dslContext; - this.sqlDialect = sqlDialect; - this.schema = schema; - } - - public IPhysicalPlan plan(Interval interval, ILogicalPlan logicalPlan) { - this.interval = interval; - return logicalPlan.accept(this); - } - - @Override - public IPhysicalPlan visitAggregate(LogicalAggregate aggregate) { - IPhysicalPlan physicalPlan = aggregate.input().accept(this); - if (physicalPlan instanceof JdbcReadStep jdbcReadStep) { - // PUSH the aggregate down to the table scan step - SelectStatement aggregation = pushdownAggregationOverScan( - jdbcReadStep.getSelectStatement(), - aggregate - ); - - return new JdbcReadStep(dslContext, - sqlDialect, - aggregation); - } - - return super.visitAggregate(aggregate); - } - - @Override - public IPhysicalPlan visitTableScan(LogicalTableScan tableScan) { - SelectStatement selectStatement = new SelectStatement(); - selectStatement.getFrom().setExpression(new TableIdentifier(tableScan.table())); - selectStatement.getFrom().setAlias(tableScan.table()); - selectStatement.getWhere().and( - new ComparisonExpression.GTE(this.interval.getTimestampColumn(), sqlDialect.toISO8601TimestampExpression(this.interval.getStartTime())), - new ComparisonExpression.LT(this.interval.getTimestampColumn(), sqlDialect.toISO8601TimestampExpression(this.interval.getEndTime())), - tableScan.filter()); - selectStatement.getSelectorList().addAll(tableScan.selectorList()); - - return new JdbcReadStep( - dslContext, - sqlDialect, - selectStatement - ); - } - - @Override - public IPhysicalPlan visitBinaryOp(LogicalBinaryOp binaryOp) { - return switch (binaryOp.op()) { - case ADD -> new ArithmeticStep.Add(binaryOp.left().accept(this), - binaryOp.right().accept(this)); - case SUB -> new ArithmeticStep.Sub(binaryOp.left().accept(this), - binaryOp.right().accept(this)); - case MUL -> new ArithmeticStep.Mul(binaryOp.left().accept(this), - binaryOp.right().accept(this)); - case DIV -> new ArithmeticStep.Div(binaryOp.left().accept(this), - binaryOp.right().accept(this)); - }; - } - - @Override - public IPhysicalPlan visitScalar(LogicalScalar scalar) { - return new LiteralQueryStep(scalar.literal()); - } - - private SelectStatement pushdownAggregationOverScan(SelectStatement select, LogicalAggregate aggregate) { - SelectStatementBuilder builder = new SelectStatementBuilder(); - return builder.sqlDialect(this.sqlDialect) - .schema(this.schema) - .interval(this.interval) - .fields(List.of(new Selector(new ExpressionNode(this.schema, - StringUtils.format("%s(%s)", aggregate.func(), aggregate.field().getName())), - aggregate.field().getName(), - aggregate.field().getDataType()))) - .filter(LogicalExpression.create(LogicalExpression.AND.OP, select.getWhere().getExpressions())) - .groupBy(aggregate.groupBy()) - .build(); - } -} diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java index 728800a678..047bb03748 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java @@ -89,7 +89,7 @@ public static class MetricQueryRequest { public QueryResponse timeSeries(@Validated @RequestBody MetricQueryRequest request) throws Exception { IPhysicalPlan pipeline = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(request.getInterval()) + .interval(request.getInterval()) .condition(request.getCondition()) .build(request.getExpression()); diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java index 234d05b815..c0b9147622 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java @@ -21,9 +21,19 @@ import org.bithon.component.commons.expression.ComparisonExpression; import org.bithon.component.commons.expression.ConditionalExpression; import org.bithon.component.commons.expression.IExpression; +import org.bithon.component.commons.expression.IdentifierExpression; import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.utils.StringUtils; +import org.bithon.server.datasource.ISchemaProvider; +import org.bithon.server.datasource.query.IDataSourceReader; import org.bithon.server.datasource.query.Interval; +import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; +import org.bithon.server.datasource.query.plan.logical.ILogicalPlanVisitor; +import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; +import org.bithon.server.datasource.query.plan.logical.LogicalBinaryOp; +import org.bithon.server.datasource.query.plan.logical.LogicalFilter; +import org.bithon.server.datasource.query.plan.logical.LogicalScalar; +import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; import org.bithon.server.datasource.query.plan.physical.ArithmeticStep; import org.bithon.server.datasource.query.plan.physical.FilterStep; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; @@ -35,6 +45,7 @@ import org.bithon.server.metric.expression.ast.MetricExpressionASTBuilder; import org.bithon.server.metric.expression.ast.MetricExpressionOptimizer; import org.bithon.server.metric.expression.ast.PredicateEnum; +import org.bithon.server.metric.expression.plan.LogicalPlanBuilder; import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; import org.bithon.server.web.service.datasource.api.QueryField; @@ -53,6 +64,7 @@ public class PhysicalPlanner { private IntervalRequest intervalRequest; private String condition; private QueryPipelineBuilderSettings settings = QueryPipelineBuilderSettings.DEFAULT; + private ISchemaProvider schemaProvider; public static PhysicalPlanner builder() { return new PhysicalPlanner(); @@ -63,7 +75,7 @@ public PhysicalPlanner dataSourceApi(IDataSourceApi dataSourceApi) { return this; } - public PhysicalPlanner intervalRequest(IntervalRequest intervalRequest) { + public PhysicalPlanner interval(IntervalRequest intervalRequest) { this.intervalRequest = intervalRequest; return this; } @@ -78,6 +90,11 @@ public PhysicalPlanner settings(QueryPipelineBuilderSettings settings) { return this; } + public PhysicalPlanner schemaProvider(ISchemaProvider schemaProvider) { + this.schemaProvider = schemaProvider; + return this; + } + public IPhysicalPlan build(String expression) { IExpression expr = MetricExpressionASTBuilder.parse(expression); @@ -88,6 +105,87 @@ public IPhysicalPlan build(String expression) { return this.build(expr); } + public IPhysicalPlan timeSeries(String expression) { + IExpression expr = MetricExpressionASTBuilder.parse(expression); + + // Apply optimization like constant folding on parsed expression + // The optimization is applied here so that the above parse can be tested separately + expr = MetricExpressionOptimizer.optimize(expr); + + ILogicalPlan logicalPlan = expr.accept(new LogicalPlanBuilder(schemaProvider)); + return logicalPlan.accept(new PhysicalPlanImpl(Interval.of(this.intervalRequest.getStartISO8601(), + this.intervalRequest.getEndISO8601(), + this.intervalRequest.calculateStep(), + new IdentifierExpression("timestamp")))); + } + + public IPhysicalPlan groupBy(String expression) { + IExpression expr = MetricExpressionASTBuilder.parse(expression); + + // Apply optimization like constant folding on parsed expression + // The optimization is applied here so that the above parse can be tested separately + expr = MetricExpressionOptimizer.optimize(expr); + + ILogicalPlan logicalPlan = expr.accept(new LogicalPlanBuilder(schemaProvider)); + return logicalPlan.accept(new PhysicalPlanImpl(Interval.of(this.intervalRequest.getStartISO8601(), + this.intervalRequest.getEndISO8601(), + null, + new IdentifierExpression("timestamp")))); + } + + static class PhysicalPlanImpl implements ILogicalPlanVisitor { + private final Interval interval; + + PhysicalPlanImpl(Interval interval) { + this.interval = interval; + } + + @Override + public IPhysicalPlan visitAggregate(LogicalAggregate aggregate) { + IPhysicalPlan physicalPlan = aggregate.input().accept(this); + if (physicalPlan.canPushDownAggregate(aggregate)) { + return physicalPlan.pushDownAggregate(aggregate); + } else { + throw new UnsupportedOperationException("Aggregate not supported: " + aggregate); + } + } + + @Override + public IPhysicalPlan visitTableScan(LogicalTableScan tableScan) { + try { + try (IDataSourceReader reader = tableScan.table().getDataStoreSpec().createReader()) { + return reader.plan(tableScan, interval); + } + } catch (Exception e) { + throw new RuntimeException("Failed to create reader for table: " + tableScan.table().getName(), e); + } + } + + @Override + public IPhysicalPlan visitBinaryOp(LogicalBinaryOp binaryOp) { + return switch (binaryOp.op()) { + case ADD -> new ArithmeticStep.Add(binaryOp.left().accept(this), + binaryOp.right().accept(this)); + case SUB -> new ArithmeticStep.Sub(binaryOp.left().accept(this), + binaryOp.right().accept(this)); + case MUL -> new ArithmeticStep.Mul(binaryOp.left().accept(this), + binaryOp.right().accept(this)); + case DIV -> new ArithmeticStep.Div(binaryOp.left().accept(this), + binaryOp.right().accept(this)); + }; + } + + @Override + public IPhysicalPlan visitScalar(LogicalScalar scalar) { + return new LiteralQueryStep(scalar.literal()); + } + + @Override + public IPhysicalPlan visitFilter(LogicalFilter filter) { + return null; + } + } + public IPhysicalPlan build(IExpression expression) { return expression.accept(new Builder()); } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java index 11f1b46177..8a71380233 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java @@ -19,7 +19,9 @@ import org.bithon.component.commons.expression.ArithmeticExpression; import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.utils.CollectionUtils; +import org.bithon.component.commons.utils.Preconditions; import org.bithon.server.datasource.ISchema; +import org.bithon.server.datasource.ISchemaProvider; import org.bithon.server.datasource.column.IColumn; import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.logical.BinaryOp; @@ -40,16 +42,19 @@ * @date 2025/6/4 23:43 */ public class LogicalPlanBuilder implements IMetricExpressionVisitor { - private final ISchema schema; + private final ISchemaProvider schemaProvider; - public LogicalPlanBuilder(ISchema schema) { - this.schema = schema; + public LogicalPlanBuilder(ISchemaProvider schemaProvider) { + this.schemaProvider = schemaProvider; } @Override public ILogicalPlan visit(MetricSelectExpression expression) { + ISchema schema = Preconditions.checkNotNull(schemaProvider.getSchema(expression.getFrom()), + "Schema for %s is not found", + expression.getFrom()); return new LogicalTableScan( - expression.getFrom(), + schema, List.of(new Selector(expression.getMetric(), expression.getMetric(), expression.getDataType())), expression.getLabelSelectorExpression() ); @@ -57,10 +62,13 @@ public ILogicalPlan visit(MetricSelectExpression expression) { @Override public ILogicalPlan visit(MetricAggregateExpression expression) { + ISchema schema = Preconditions.checkNotNull(schemaProvider.getSchema(expression.getFrom()), + "Schema for %s is not found", + expression.getFrom()); IColumn col = schema.getColumnByName(expression.getMetric().getName()); return new LogicalAggregate( expression.getMetric().getAggregator(), - new LogicalTableScan(expression.getFrom(), + new LogicalTableScan(schema, List.of(new Selector(expression.getMetric().getName(), expression.getMetric().getName(), expression.getDataType())), diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java index c29474dc58..3546cd41b0 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java @@ -57,11 +57,11 @@ public void test_ScalarOverLiteral_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -77,11 +77,11 @@ public void test_ScalarOverLiteral_Add_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 3.3"); PipelineQueryResult response = evaluator.execute().get(); @@ -97,11 +97,11 @@ public void test_ScalarOverLiteral_Add_Double_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -117,11 +117,11 @@ public void test_ScalarOverLiteral_Add_Double_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 2.2"); PipelineQueryResult response = evaluator.execute().get(); @@ -137,11 +137,11 @@ public void test_ScalarOverSizeLiteral_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5Mi"); PipelineQueryResult response = evaluator.execute().get(); @@ -157,11 +157,11 @@ public void test_ScalarOverPercentageLiteral_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 90%"); PipelineQueryResult response = evaluator.execute().get(); @@ -177,11 +177,11 @@ public void test_ScalarOverDurationLiteral_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 1h"); PipelineQueryResult response = evaluator.execute().get(); @@ -197,11 +197,11 @@ public void test_ScalarOverLiteral_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -217,11 +217,11 @@ public void test_ScalarOverLiteral_Sub_Double_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 2.2"); PipelineQueryResult response = evaluator.execute().get(); @@ -237,11 +237,11 @@ public void test_ScalarOverLiteral_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -257,11 +257,11 @@ public void test_ScalarOverLiteral_Mul_Double_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -277,11 +277,11 @@ public void test_ScalarOverLiteral_Mul_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5.5"); PipelineQueryResult response = evaluator.execute().get(); @@ -297,11 +297,11 @@ public void test_ScalarOverLiteral_Mul_Double_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 3"); PipelineQueryResult response = evaluator.execute().get(); @@ -317,11 +317,11 @@ public void test_ScalarOverLiteral_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -337,11 +337,11 @@ public void test_ScalarOverLiteral_Div_Double_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20"); PipelineQueryResult response = evaluator.execute().get(); @@ -357,11 +357,11 @@ public void test_ScalarOverLiteral_Div_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20.0"); PipelineQueryResult response = evaluator.execute().get(); @@ -377,11 +377,11 @@ public void test_ScalarOverLiteral_Div_Double_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 3.0"); PipelineQueryResult response = evaluator.execute().get(); @@ -414,11 +414,11 @@ public void test_VectorOverLiteral_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -463,11 +463,11 @@ public void test_VectorOverLiteral_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" @@ -512,11 +512,11 @@ public void test_VectorOverLiteral_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" @@ -561,11 +561,11 @@ public void test_VectorOverLiteral_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" @@ -609,11 +609,11 @@ public void test_ScalarOverScalar_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "+" @@ -650,11 +650,11 @@ public void test_ScalarOverScalar_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "-" @@ -689,11 +689,11 @@ public void test_ScalarOverScalar_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "*" @@ -728,11 +728,11 @@ public void test_ScalarOverScalar_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" @@ -769,11 +769,11 @@ public void test_ScalarOverVector_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "+" @@ -820,11 +820,11 @@ public void test_ScalarOverVector_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "-" @@ -871,11 +871,11 @@ public void test_ScalarOverVector_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "*" @@ -922,11 +922,11 @@ public void test_ScalarOverVector_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" @@ -973,11 +973,11 @@ public void test_ScalarOverVector_Div_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" @@ -1024,11 +1024,11 @@ public void test_ScalarOverVector_Div_Double_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" @@ -1073,11 +1073,11 @@ public void test_VectorOverScalar_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -1122,11 +1122,11 @@ public void test_VectorOverScalar_Add_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -1171,11 +1171,11 @@ public void test_VectorOverScalar_Add_Double_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -1220,11 +1220,11 @@ public void test_VectorOverScalar_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" @@ -1269,11 +1269,11 @@ public void test_VectorOverScalar_Sub_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" @@ -1318,11 +1318,11 @@ public void test_VectorOverScalar_Sub_Double_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" @@ -1367,11 +1367,11 @@ public void test_VectorOverScalar_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" @@ -1416,11 +1416,11 @@ public void test_VectorOverScalar_Mul_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" @@ -1465,11 +1465,11 @@ public void test_VectorOverScalar_Mul_Double_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" @@ -1515,11 +1515,11 @@ public void test_VectorOverScalar_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" @@ -1565,11 +1565,11 @@ public void test_VectorOverScalar_Div_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" @@ -1615,11 +1615,11 @@ public void test_VectorOverScalar_Div_Double_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" @@ -1667,11 +1667,11 @@ public void test_VectorOverVector_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -1718,11 +1718,11 @@ public void test_VectorOverVector_Add_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -1770,11 +1770,11 @@ public void test_VectorOverVector_Add_Double_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -1821,11 +1821,11 @@ public void test_VectorOverVector_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" @@ -1872,11 +1872,11 @@ public void test_VectorOverVector_Sub_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" @@ -1923,11 +1923,11 @@ public void test_VectorOverVector_Sub_Double_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" @@ -1974,11 +1974,11 @@ public void test_VectorOverVector_Sub_Double_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" @@ -2025,11 +2025,11 @@ public void test_VectorOverVector_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" @@ -2076,11 +2076,11 @@ public void test_VectorOverVector_Mul_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" @@ -2127,11 +2127,11 @@ public void test_VectorOverVector_Mul_Double_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" @@ -2179,11 +2179,11 @@ public void test_VectorOverVector_Mul_Double_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" @@ -2230,11 +2230,11 @@ public void test_VectorOverVector_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" @@ -2281,11 +2281,11 @@ public void test_VectorOverVector_Div_Long_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" @@ -2332,11 +2332,11 @@ public void test_VectorOverVector_Div_Double_Long() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" @@ -2383,11 +2383,11 @@ public void test_VectorOverVector_Div_Double_Double() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" @@ -2434,11 +2434,11 @@ public void test_VectorOverVector_NoIntersection() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" @@ -2493,11 +2493,11 @@ public void test_VectorOverVector_NoIntersection_2() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" @@ -2554,11 +2554,11 @@ public void test_VectorOverVector_NoIntersection_3() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" @@ -2610,11 +2610,11 @@ public void test_MultipleExpressions() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + "/ " @@ -2659,11 +2659,11 @@ public void test_RelativeComparison() throws Exception { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) > -5%[-1d]"); PipelineQueryResult response = evaluator.execute() @@ -2708,11 +2708,11 @@ public void test_FilterStep_Double_GT() throws Exception { { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 2"); PipelineQueryResult response = evaluator.execute().get(); @@ -2736,11 +2736,11 @@ public void test_FilterStep_Double_GT() throws Exception { { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 3"); PipelineQueryResult response = evaluator.execute().get(); @@ -2760,11 +2760,11 @@ public void test_FilterStep_Double_GT() throws Exception { { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 4"); PipelineQueryResult response = evaluator.execute().get(); @@ -2784,11 +2784,11 @@ public void test_FilterStep_Double_GT() throws Exception { { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -2818,11 +2818,11 @@ public void test_FilterStep_Double_GTE() throws Exception { { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 2"); PipelineQueryResult response = evaluator.execute().get(); @@ -2843,11 +2843,11 @@ public void test_FilterStep_Double_GTE() throws Exception { { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 3"); PipelineQueryResult response = evaluator.execute().get(); @@ -2867,11 +2867,11 @@ public void test_FilterStep_Double_GTE() throws Exception { { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 4"); PipelineQueryResult response = evaluator.execute().get(); @@ -2891,11 +2891,11 @@ public void test_FilterStep_Double_GTE() throws Exception { { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -2914,11 +2914,11 @@ public void test_FilterStep_Double_GTE() throws Exception { { IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) - .intervalRequest(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .interval(IntervalRequest.builder() + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 6"); PipelineQueryResult response = evaluator.execute().get(); diff --git a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java deleted file mode 100644 index ba0bc89262..0000000000 --- a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/JdbcPhysicalPlannerTest.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2020 bithon.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bithon.server.storage.jdbc.common.statement.builder; - -import org.bithon.component.commons.expression.IDataType; -import org.bithon.component.commons.expression.IExpression; -import org.bithon.component.commons.expression.IdentifierExpression; -import org.bithon.server.commons.time.TimeSpan; -import org.bithon.server.datasource.DefaultSchema; -import org.bithon.server.datasource.ISchema; -import org.bithon.server.datasource.TimestampSpec; -import org.bithon.server.datasource.column.ExpressionColumn; -import org.bithon.server.datasource.column.StringColumn; -import org.bithon.server.datasource.column.aggregatable.last.AggregateLongLastColumn; -import org.bithon.server.datasource.column.aggregatable.sum.AggregateLongSumColumn; -import org.bithon.server.datasource.query.IDataSourceReader; -import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; -import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.setting.QuerySettings; -import org.bithon.server.datasource.reader.clickhouse.AggregateFunctionColumn; -import org.bithon.server.datasource.reader.h2.H2SqlDialect; -import org.bithon.server.datasource.reader.jdbc.JdbcDataSourceReader; -import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; -import org.bithon.server.datasource.reader.jdbc.statement.JdbcPhysicalPlanner; -import org.bithon.server.datasource.store.IDataStoreSpec; -import org.bithon.server.metric.expression.ast.MetricExpressionASTBuilder; -import org.bithon.server.metric.expression.plan.LogicalPlanBuilder; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.util.Arrays; - -/** - * @author frank.chen021@outlook.com - * @date 2025/6/5 20:58 - */ -public class JdbcPhysicalPlannerTest { - private final ISchema schema = new DefaultSchema("bithon-jvm-metrics", - "bithon-jvm-metrics", - new TimestampSpec("timestamp"), - Arrays.asList(new StringColumn("appName", "appName"), - new StringColumn("instance", "instance")), - Arrays.asList(new AggregateLongSumColumn("responseTime", "responseTime"), - new AggregateLongSumColumn("totalCount", "totalCount"), - new AggregateLongSumColumn("count4xx", "count4xx"), - new AggregateLongSumColumn("count5xx", "count5xx"), - new AggregateLongLastColumn("activeThreads", "activeThreads"), - new AggregateLongLastColumn("totalThreads", "totalThreads"), - new ExpressionColumn("avgResponseTime", - null, - "sum(responseTime) / sum(totalCount)", - "double"), - - // For ClickHouse data source - new AggregateFunctionColumn("clickedSum", "clickedSum", "sum", IDataType.LONG), - new AggregateFunctionColumn("clickedCnt", "clickedCnt", "count", IDataType.LONG) - ), - null, - new IDataStoreSpec() { - @Override - public String getStore() { - return "bithon_jvm_metrics"; - } - - @Override - public void setSchema(ISchema schema) { - - } - - @Override - public boolean isInternal() { - return false; - } - - @Override - public IDataSourceReader createReader() { - return new JdbcDataSourceReader(null, new H2SqlDialect(), QuerySettings.DEFAULT); - } - }, - null, - null); - - private final ISqlDialect h2Dialect = new H2SqlDialect(); - - @Test - public void testTableScanPlan() { - IExpression ast = MetricExpressionASTBuilder.parse("bithon-jvm-metrics.totalCount"); - ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); - - IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) - .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), - TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), - logicalPlan); - - Assertions.assertEquals(""" - JdbcReadStep - SELECT "totalCount" AS "totalCount" - FROM "bithon-jvm-metrics" AS "bithon-jvm-metrics" - WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') - """, - physicalPlan.serializeToText()); - } - - @Test - public void testTableScanPlanWithFilter() { - IExpression ast = MetricExpressionASTBuilder.parse("bithon-jvm-metrics.totalCount{appName=\"jacky\"}"); - ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); - IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) - .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), - TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), - logicalPlan); - - Assertions.assertEquals(""" - JdbcReadStep - SELECT "totalCount" AS "totalCount" - FROM "bithon-jvm-metrics" AS "bithon-jvm-metrics" - WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("appName" = 'jacky') - """, - physicalPlan.serializeToText()); - } - - @Test - public void test_SumAggregate() { - IExpression ast = MetricExpressionASTBuilder.parse("sum(bithon-jvm-metrics.totalCount{appName=\"jacky\"})"); - ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); - IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) - .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), - TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), - logicalPlan); - - // TODO: Remove duplicate timestamp filter - Assertions.assertEquals(""" - JdbcReadStep - SELECT sum("totalCount") AS "totalCount" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - """, - physicalPlan.serializeToText()); - } - - @Test - public void test_SumAggregate_GroupBy() { - IExpression ast = MetricExpressionASTBuilder.parse("sum(bithon-jvm-metrics.totalCount{appName=\"jacky\"}) by (appName)"); - ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); - IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) - .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), - TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), - logicalPlan); - - // TODO: Remove duplicate timestamp filter - Assertions.assertEquals(""" - JdbcReadStep - SELECT "appName", - sum("totalCount") AS "totalCount" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - GROUP BY "appName" - """, - physicalPlan.serializeToText()); - } - - @Test - public void test_SumAggregate_GroupBy_TimeSeries() { - IExpression ast = MetricExpressionASTBuilder.parse("sum(bithon-jvm-metrics.totalCount{appName=\"jacky\"}) by (appName)"); - ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); - IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) - .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), - TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800"), - Duration.ofSeconds(10), - new IdentifierExpression("timestamp")), - logicalPlan); - - // TODO: Remove duplicate timestamp filter - Assertions.assertEquals(""" - JdbcReadStep - SELECT UNIX_TIMESTAMP("timestamp")/ 10 * 10 AS "_timestamp", - "appName", - sum("totalCount") AS "totalCount" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - GROUP BY "appName", "_timestamp" - """, - physicalPlan.serializeToText()); - } - - @Test - public void test_LastAggregate() { - IExpression ast = MetricExpressionASTBuilder.parse("last(bithon-jvm-metrics.totalCount{appName=\"jacky\"}) by (appName)"); - ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); - IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) - .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), - TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), - logicalPlan); - - // TODO: Remove duplicate timestamp filter - Assertions.assertEquals(""" - JdbcReadStep - SELECT "appName", - "totalCount" - FROM - ( - SELECT "appName", - FIRST_VALUE("totalCount") OVER (PARTITION BY (UNIX_TIMESTAMP("timestamp") / 600) * 600 ORDER BY "timestamp" ASC) AS "totalCount" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - ) - GROUP BY "appName", "totalCount" - """, - physicalPlan.serializeToText()); - } - - @Test - public void test_Arithmetic_TwoExpressions() { - IExpression ast = MetricExpressionASTBuilder.parse("sum(bithon-jvm-metrics.count4xx{appName=\"jacky\"}) + sum(bithon-jvm-metrics.count5xx{appName=\"jacky\"})"); - ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); - IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) - .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), - TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), - logicalPlan); - - Assertions.assertEquals(""" - AddStep, Result Column: value, Retained Columns: [] - lhs:\s - JdbcReadStep - SELECT sum("count4xx") AS "count4xx" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - rhs:\s - JdbcReadStep - SELECT sum("count5xx") AS "count5xx" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - """, - physicalPlan.serializeToText()); - } - - @Test - public void test_Arithmetic_WithLiteral() { - IExpression ast = MetricExpressionASTBuilder.parse("sum(bithon-jvm-metrics.count4xx{appName=\"jacky\"}) + 5"); - ILogicalPlan logicalPlan = ast.accept(new LogicalPlanBuilder(this.schema)); - IPhysicalPlan physicalPlan = new JdbcPhysicalPlanner(null, this.h2Dialect, this.schema) - .plan(Interval.of(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800"), - TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")), - logicalPlan); - - Assertions.assertEquals(""" - AddStep, Result Column: value, Retained Columns: [] - lhs:\s - JdbcReadStep - SELECT sum("count4xx") AS "count4xx" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - rhs:\s - 5 - """, - physicalPlan.serializeToText()); - } -} diff --git a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java new file mode 100644 index 0000000000..7286d6379f --- /dev/null +++ b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java @@ -0,0 +1,301 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.storage.jdbc.common.statement.builder; + +import org.bithon.component.commons.expression.IDataType; +import org.bithon.server.commons.time.TimeSpan; +import org.bithon.server.datasource.DefaultSchema; +import org.bithon.server.datasource.ISchema; +import org.bithon.server.datasource.ISchemaProvider; +import org.bithon.server.datasource.SchemaException; +import org.bithon.server.datasource.TimestampSpec; +import org.bithon.server.datasource.column.ExpressionColumn; +import org.bithon.server.datasource.column.StringColumn; +import org.bithon.server.datasource.column.aggregatable.last.AggregateLongLastColumn; +import org.bithon.server.datasource.column.aggregatable.sum.AggregateLongSumColumn; +import org.bithon.server.datasource.query.IDataSourceReader; +import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.query.setting.QuerySettings; +import org.bithon.server.datasource.reader.clickhouse.AggregateFunctionColumn; +import org.bithon.server.datasource.reader.h2.H2SqlDialect; +import org.bithon.server.datasource.reader.jdbc.JdbcDataSourceReader; +import org.bithon.server.datasource.store.IDataStoreSpec; +import org.bithon.server.metric.expression.pipeline.PhysicalPlanner; +import org.bithon.server.web.service.datasource.api.IntervalRequest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +/** + * @author frank.chen021@outlook.com + * @date 2025/6/5 20:58 + */ +public class PhysicalPlannerTest { + private final ISchema schema = new DefaultSchema("bithon-jvm-metrics", + "bithon-jvm-metrics", + new TimestampSpec("timestamp"), + Arrays.asList(new StringColumn("appName", "appName"), + new StringColumn("instance", "instance")), + Arrays.asList(new AggregateLongSumColumn("responseTime", "responseTime"), + new AggregateLongSumColumn("totalCount", "totalCount"), + new AggregateLongSumColumn("count4xx", "count4xx"), + new AggregateLongSumColumn("count5xx", "count5xx"), + new AggregateLongLastColumn("activeThreads", "activeThreads"), + new AggregateLongLastColumn("totalThreads", "totalThreads"), + new ExpressionColumn("avgResponseTime", + null, + "sum(responseTime) / sum(totalCount)", + "double"), + + // For ClickHouse data source + new AggregateFunctionColumn("clickedSum", "clickedSum", "sum", IDataType.LONG), + new AggregateFunctionColumn("clickedCnt", "clickedCnt", "count", IDataType.LONG) + ), + null, + new IDataStoreSpec() { + @Override + public String getStore() { + return "bithon_jvm_metrics"; + } + + @Override + public void setSchema(ISchema schema) { + + } + + @Override + public boolean isInternal() { + return false; + } + + @Override + public IDataSourceReader createReader() { + return new JdbcDataSourceReader(null, new H2SqlDialect(), QuerySettings.DEFAULT); + } + }, + null, + null); + + private final ISchemaProvider schemaProvider = name -> { + if ("bithon-jvm-metrics".equals(name)) { + return this.schema; + } + throw new SchemaException.NotFound(name); + }; + + @Test + public void testTableScanPlan() { + IPhysicalPlan physicalPlan = PhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) + .timeSeries("bithon-jvm-metrics.totalCount"); + + Assertions.assertEquals(""" + JdbcReadStep + SELECT "totalCount" AS "totalCount" + FROM "bithon-jvm-metrics" AS "bithon-jvm-metrics" + WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') + """, + physicalPlan.serializeToText()); + } + + @Test + public void testTableScanPlanWithFilter() { + IPhysicalPlan physicalPlan = PhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) + .timeSeries("bithon-jvm-metrics.totalCount{appName=\"jacky\"}"); + + Assertions.assertEquals(""" + JdbcReadStep + SELECT "totalCount" AS "totalCount" + FROM "bithon-jvm-metrics" AS "bithon-jvm-metrics" + WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("appName" = 'jacky') + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_SumAggregate() { + String expr = """ + sum(bithon-jvm-metrics.totalCount{appName="jacky"}) + """; + IPhysicalPlan physicalPlan = PhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) + .groupBy(expr); + + Assertions.assertEquals(""" + JdbcReadStep + SELECT sum("totalCount") AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_SumAggregate_GroupBy() { + String expr = """ + sum(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) + """; + IPhysicalPlan physicalPlan = PhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .step(60) + .build()) + .groupBy(expr); + Assertions.assertEquals(""" + JdbcReadStep + SELECT "appName", + sum("totalCount") AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + GROUP BY "appName" + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_SumAggregate_GroupBy_TimeSeries() { + String expr = """ + sum(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) + """; + IPhysicalPlan physicalPlan = PhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .step(10) + .build()) + .timeSeries(expr); + + Assertions.assertEquals(""" + JdbcReadStep + SELECT UNIX_TIMESTAMP("timestamp")/ 10 * 10 AS "_timestamp", + "appName", + sum("totalCount") AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + GROUP BY "appName", "_timestamp" + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_LastAggregate() { + String expr = """ + last(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) + """; + + IPhysicalPlan physicalPlan = PhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) + .groupBy(expr); + + Assertions.assertEquals(""" + JdbcReadStep + SELECT "appName", + "totalCount" + FROM + ( + SELECT "appName", + FIRST_VALUE("totalCount") OVER (PARTITION BY (UNIX_TIMESTAMP("timestamp") / 600) * 600 ORDER BY "timestamp" ASC) AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + ) + GROUP BY "appName", "totalCount" + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_Arithmetic_TwoExpressions() { + String expr = """ + sum(bithon-jvm-metrics.count4xx{appName="jacky"}) + + + sum(bithon-jvm-metrics.count5xx{appName="jacky"}) + """; + + IPhysicalPlan physicalPlan = PhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) + .groupBy(expr); + + Assertions.assertEquals(""" + AddStep, Result Column: value, Retained Columns: [] + lhs:\s + JdbcReadStep + SELECT sum("count4xx") AS "count4xx" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + rhs:\s + JdbcReadStep + SELECT sum("count5xx") AS "count5xx" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_Arithmetic_WithLiteral() { + String expr = """ + sum(bithon-jvm-metrics.count4xx{appName="jacky"}) + + + 5 + """; + + IPhysicalPlan physicalPlan = PhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) + .groupBy(expr); + + Assertions.assertEquals(""" + AddStep, Result Column: value, Retained Columns: [] + lhs:\s + JdbcReadStep + SELECT sum("count4xx") AS "count4xx" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + rhs:\s + 5 + """, + physicalPlan.serializeToText()); + } +} diff --git a/server/storage/src/main/java/org/bithon/server/storage/datasource/SchemaManager.java b/server/storage/src/main/java/org/bithon/server/storage/datasource/SchemaManager.java index efa899306f..fd449c466e 100644 --- a/server/storage/src/main/java/org/bithon/server/storage/datasource/SchemaManager.java +++ b/server/storage/src/main/java/org/bithon/server/storage/datasource/SchemaManager.java @@ -22,6 +22,7 @@ import org.bithon.component.commons.time.DateTime; import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.ISchema; +import org.bithon.server.datasource.ISchemaProvider; import org.bithon.server.datasource.SchemaException; import org.springframework.context.SmartLifecycle; @@ -40,7 +41,7 @@ * @date 2020-08-21 15:13:41 */ @Slf4j -public class SchemaManager implements SmartLifecycle { +public class SchemaManager implements SmartLifecycle, ISchemaProvider { private final List listeners = Collections.synchronizedList(new ArrayList<>()); private final ISchemaStorage schemaStorage; private ScheduledExecutorService loaderScheduler; From 1c32aa7939b2cd9d4a30389d4ee51268e268d418 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Thu, 12 Jun 2025 00:16:04 +0800 Subject: [PATCH 18/22] Fix ArithmeticStepTest --- .../query/plan/physical/MetricQueryStep.java | 18 +- .../expression/pipeline/PhysicalPlanner.java | 20 +- .../expression/plan/LogicalPlanBuilder.java | 27 + .../pipeline/step/ArithmeticStepTest.java | 2353 +++++++++++------ 4 files changed, 1548 insertions(+), 870 deletions(-) diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/MetricQueryStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/MetricQueryStep.java index a3d8e301d0..3c6f8b6dc3 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/MetricQueryStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/MetricQueryStep.java @@ -19,6 +19,7 @@ import org.bithon.component.commons.utils.CollectionUtils; import org.bithon.component.commons.utils.HumanReadableDuration; +import org.bithon.server.commons.time.TimeSpan; import org.bithon.server.datasource.query.Interval; import org.bithon.server.datasource.query.result.PipelineQueryResult; @@ -96,17 +97,10 @@ private boolean computeIsScalar() { return false; } - /* - if (interval.getBucketCount() != null && interval.getBucketCount() == 1) { - // ONLY one bucket is requested, the result set is a scalar - return true; - } - - TimeSpan start = interval.getStartISO8601(); - TimeSpan end = interval.getEndISO8601(); - long intervalLength = (end.getMilliseconds() - start.getMilliseconds()) / 1000;= - return interval.getStep() != null && interval.getStep() == intervalLength;*/ - return true; + TimeSpan start = interval.getStartTime(); + TimeSpan end = interval.getEndTime(); + long intervalLength = (end.getMilliseconds() - start.getMilliseconds()) / 1000; + return interval.getStep() != null && interval.getStep().getSeconds() == intervalLength; } @Override @@ -120,7 +114,6 @@ public CompletableFuture execute() { synchronized (this) { if (cachedResponse == null) { cachedResponse = CompletableFuture.supplyAsync(() -> { - return null; /*try { QueryRequest queryRequest = QueryRequest.builder() @@ -151,6 +144,7 @@ public CompletableFuture execute() { } catch (IOException e) { throw new RuntimeException(e); }*/ + return null; }); } } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java index c0b9147622..7f086d833a 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java @@ -182,7 +182,25 @@ public IPhysicalPlan visitScalar(LogicalScalar scalar) { @Override public IPhysicalPlan visitFilter(LogicalFilter filter) { - return null; + switch(filter.op()) { + case LT: + return new FilterStep.LT(filter.left().accept(this), + (Number) ((LogicalScalar) filter.right()).literal().getValue()); + case LTE: + return new FilterStep.LTE(filter.left().accept(this), + (Number) ((LogicalScalar) filter.right()).literal().getValue()); + case GT: + return new FilterStep.GT(filter.left().accept(this), + (Number) ((LogicalScalar) filter.right()).literal().getValue()); + case GTE: + return new FilterStep.GTE(filter.left().accept(this), + (Number) ((LogicalScalar) filter.right()).literal().getValue()); + case NE: + return new FilterStep.NE(filter.left().accept(this), + (Number) ((LogicalScalar) filter.right()).literal().getValue()); + default: + throw new UnsupportedOperationException("Unsupported filter operation: " + filter.op()); + } } } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java index 8a71380233..32366d4144 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java @@ -17,6 +17,7 @@ package org.bithon.server.metric.expression.plan; import org.bithon.component.commons.expression.ArithmeticExpression; +import org.bithon.component.commons.expression.ConditionalExpression; import org.bithon.component.commons.expression.LiteralExpression; import org.bithon.component.commons.utils.CollectionUtils; import org.bithon.component.commons.utils.Preconditions; @@ -28,10 +29,13 @@ import org.bithon.server.datasource.query.plan.logical.ILogicalPlan; import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; import org.bithon.server.datasource.query.plan.logical.LogicalBinaryOp; +import org.bithon.server.datasource.query.plan.logical.LogicalFilter; import org.bithon.server.datasource.query.plan.logical.LogicalScalar; import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; +import org.bithon.server.datasource.query.plan.logical.PredicateEnum; import org.bithon.server.metric.expression.ast.IMetricExpressionVisitor; import org.bithon.server.metric.expression.ast.MetricAggregateExpression; +import org.bithon.server.metric.expression.ast.MetricExpectedExpression; import org.bithon.server.metric.expression.ast.MetricSelectExpression; import java.util.ArrayList; @@ -95,6 +99,29 @@ public ILogicalPlan visit(ArithmeticExpression expression) { ); } + @Override + public ILogicalPlan visit(ConditionalExpression expression) { + PredicateEnum predicate = switch(expression.getType()) { + case "<" -> PredicateEnum.LT; + case "<=" -> PredicateEnum.LTE; + case ">" -> PredicateEnum.GT; + case ">=" -> PredicateEnum.GTE; + case "=" -> PredicateEnum.EQ; + case "!=", "<>" -> PredicateEnum.NE; + default -> throw new IllegalArgumentException("Unsupported predicate type: " + expression.getType()); + }; + return new LogicalFilter( + expression.getLhs().accept(this), + predicate, + expression.getRhs().accept(this) + ); + } + + @Override + public ILogicalPlan visit(MetricExpectedExpression expression) { + return new LogicalScalar(expression.getExpected()); + } + @Override public ILogicalPlan visit(LiteralExpression expression) { return new LogicalScalar(expression); diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java index 3546cd41b0..25af413aef 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java @@ -20,6 +20,16 @@ import org.bithon.component.commons.utils.HumanReadableDuration; import org.bithon.component.commons.utils.HumanReadableNumber; import org.bithon.server.commons.time.TimeSpan; +import org.bithon.server.datasource.DefaultSchema; +import org.bithon.server.datasource.ISchema; +import org.bithon.server.datasource.ISchemaProvider; +import org.bithon.server.datasource.TimestampSpec; +import org.bithon.server.datasource.column.ExpressionColumn; +import org.bithon.server.datasource.column.aggregatable.last.AggregateLongLastColumn; +import org.bithon.server.datasource.column.aggregatable.sum.AggregateLongSumColumn; +import org.bithon.server.datasource.query.IDataSourceReader; +import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; +import org.bithon.server.datasource.query.plan.logical.LogicalTableScan; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.result.Column; import org.bithon.server.datasource.query.result.ColumnarTable; @@ -27,6 +37,7 @@ import org.bithon.server.datasource.query.result.LongColumn; import org.bithon.server.datasource.query.result.PipelineQueryResult; import org.bithon.server.datasource.query.result.StringColumn; +import org.bithon.server.datasource.store.IDataStoreSpec; import org.bithon.server.metric.expression.pipeline.PhysicalPlanner; import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; @@ -36,393 +47,554 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + /** * @author frank.chen021@outlook.com * @date 4/4/25 9:27 pm */ @SuppressWarnings("PointlessArithmeticExpression") public class ArithmeticStepTest { + + private ISchema schema; + private ISchemaProvider schemaProvider; private IDataSourceApi dataSourceApi; + private IDataSourceReader dataSourceReader; @BeforeEach public void setUpClass() { dataSourceApi = Mockito.mock(IDataSourceApi.class); + dataSourceReader = Mockito.mock(IDataSourceReader.class); + schema = new DefaultSchema("bithon-jvm-metrics", + "bithon-jvm-metrics", + new TimestampSpec("timestamp"), + Arrays.asList(new org.bithon.server.datasource.column.StringColumn("appName", "appName"), + new org.bithon.server.datasource.column.StringColumn("instance", "instance")), + Arrays.asList(new AggregateLongSumColumn("responseTime", "responseTime"), + new AggregateLongSumColumn("totalCount", "totalCount"), + new AggregateLongSumColumn("count4xx", "count4xx"), + new AggregateLongSumColumn("count5xx", "count5xx"), + new AggregateLongLastColumn("activeThreads", "activeThreads"), + new AggregateLongLastColumn("totalThreads", "totalThreads"), + new ExpressionColumn("avgResponseTime", + null, + "sum(responseTime) / sum(totalCount)", + "double") + ), + null, + new IDataStoreSpec() { + @Override + public String getStore() { + return "bithon_jvm_metrics"; + } + + @Override + public void setSchema(ISchema schema) { + + } + + @Override + public boolean isInternal() { + return false; + } + + @Override + public IDataSourceReader createReader() { + return dataSourceReader; + } + }, + null, + null); + this.schemaProvider = name -> schema; + } + + static class MockReadStep implements IPhysicalPlan { + private final PipelineQueryResult result; + private final boolean isScalar; + + MockReadStep(PipelineQueryResult result) { + this.result = result; + this.isScalar = false; + } + + MockReadStep(PipelineQueryResult result, boolean isScalar) { + this.result = result; + this.isScalar = isScalar; + } + + @Override + public boolean isScalar() { + return this.isScalar; + } + + @Override + public boolean canPushDownAggregate(LogicalAggregate aggregate) { + return true; + } + + @Override + public IPhysicalPlan pushDownAggregate(LogicalAggregate aggregate) { + return this; + } + + @Override + public CompletableFuture execute() { + return CompletableFuture.completedFuture(this.result); + } } @Test public void test_ScalarOverLiteral_Add_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 1))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 1))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(6, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Add_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 1))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 1))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 3.3"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 3.3"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(4.3, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Add_Double_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - DoubleColumn.of("activeThreads", 3.7))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + DoubleColumn.of("activeThreads", 3.7))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(8.7, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Add_Double_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - DoubleColumn.of("activeThreads", 10.5))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + DoubleColumn.of("activeThreads", 10.5))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 2.2"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 2.2"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(12.7, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverSizeLiteral_Add_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 1))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 1))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5Mi"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5Mi"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(HumanReadableNumber.of("5Mi").longValue() + 1, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverPercentageLiteral_Add_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 1))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 1))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 90%"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 90%"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(1.9, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverDurationLiteral_Add_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 1))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 1))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 1h"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 1h"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(3601, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Sub_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 1))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 1))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 5"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(-4, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Sub_Double_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - DoubleColumn.of("activeThreads", 10.5))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + DoubleColumn.of("activeThreads", 10.5))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 2.2"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 2.2"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(8.3, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Mul_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 1))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 1))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(5, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Mul_Double_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - DoubleColumn.of("activeThreads", 5.5))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + DoubleColumn.of("activeThreads", 5.5))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(27.5, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Mul_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 1))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 1))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5.5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5.5"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(5.5, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Mul_Double_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - DoubleColumn.of("activeThreads", 3.5))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + DoubleColumn.of("activeThreads", 3.5))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 3"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 3"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(10.5, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Div_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 10))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 10))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 5"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(2, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Div_Double_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - DoubleColumn.of("activeThreads", 10))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + DoubleColumn.of("activeThreads", 10))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(0.5, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Div_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 10))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 10))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20.0"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20.0"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(0.5, valueCol.getDouble(0), .0000000001); } @Test public void test_ScalarOverLiteral_Div_Double_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenReturn(ColumnarTable.of(LongColumn.of("_timestamp", 1), - DoubleColumn.of("activeThreads", 10.5))); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of(LongColumn.of("_timestamp", 1), + DoubleColumn.of("activeThreads", 10.5))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 3.0"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 3.0"); PipelineQueryResult response = evaluator.execute().get(); - Column valueCol = response.getTable().getColumn("value"); + Column valueCol = response.getTable().getColumn("activeThreads"); Assertions.assertEquals(3.5, valueCol.getDouble(0), .0000000001); } @Test public void test_VectorOverLiteral_Add_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); - - String metric = request.getFields().get(0).getName(); - if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 5, 20, 25) - ); - } - if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 5) - ); - } - throw new IllegalArgumentException("Invalid metric: " + metric); - }); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 5, 20, 25))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "+" - + "5"); + .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + + "+" + + "5"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -440,38 +612,28 @@ public void test_VectorOverLiteral_Add_Long_Long() throws Exception { @Test public void test_VectorOverLiteral_Sub_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); - - String metric = request.getFields().get(0).getName(); - if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 5, 20, 25) - ); - } - if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 5) - ); - } - throw new IllegalArgumentException("Invalid metric: " + metric); - }); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 5, 20, 25))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "-" - + " 5"); + .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + + "-" + + " 5"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -489,38 +651,28 @@ public void test_VectorOverLiteral_Sub_Long_Long() throws Exception { @Test public void test_VectorOverLiteral_Mul_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); - - String metric = request.getFields().get(0).getName(); - if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 5, 20, 25) - ); - } - if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 5) - ); - } - throw new IllegalArgumentException("Invalid metric: " + metric); - }); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 5, 20, 25))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "*" - + "5"); + .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + + "*" + + "5"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -538,38 +690,28 @@ public void test_VectorOverLiteral_Mul_Long_Long() throws Exception { @Test public void test_VectorOverLiteral_Div_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) - .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); - - String metric = request.getFields().get(0).getName(); - if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 5, 24, 25) - ); - } - if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 5) - ); - } - throw new IllegalArgumentException("Invalid metric: " + metric); - }); + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) + .thenReturn(new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 5, 24, 25))) + .build())); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "5"); + .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + + "/" + + "5"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -587,35 +729,47 @@ public void test_VectorOverLiteral_Div_Long_Long() throws Exception { @Test public void test_ScalarOverScalar_Add_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 1) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 1) + )) + .build(), + true); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 11) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 11) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -626,37 +780,48 @@ public void test_ScalarOverScalar_Add_Long_Long() throws Exception { @Test public void test_ScalarOverScalar_Sub_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 1) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 1) + )) + .build(), + true); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 11) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 11) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -667,35 +832,47 @@ public void test_ScalarOverScalar_Sub_Long_Long() throws Exception { @Test public void test_ScalarOverScalar_Mul_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 2) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 2) + )) + .build(), + true); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 11) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 11) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -706,35 +883,47 @@ public void test_ScalarOverScalar_Mul_Long_Long() throws Exception { @Test public void test_ScalarOverScalar_Div_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 55) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 55) + )) + .build(), + true); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 11) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 11) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -745,37 +934,48 @@ public void test_ScalarOverScalar_Div_Long_Long() throws Exception { @Test public void test_ScalarOverVector_Add_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 3) + )) + .build(), + true); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("totalThreads", 5, 6, 7) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("totalThreads", 5, 6, 7) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -795,38 +995,49 @@ public void test_ScalarOverVector_Add_Long_Long() throws Exception { @Test public void test_ScalarOverVector_Sub_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 3) + )) + .build(), + true); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("totalThreads", 5, 6, 7) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("totalThreads", 5, 6, 7) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -846,38 +1057,49 @@ public void test_ScalarOverVector_Sub_Long_Long() throws Exception { @Test public void test_ScalarOverVector_Mul_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 3) + )) + .build(), + true); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("totalThreads", 5, 6, 7) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("totalThreads", 5, 6, 7) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -897,38 +1119,49 @@ public void test_ScalarOverVector_Mul_Long_Long() throws Exception { @Test public void test_ScalarOverVector_Div_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("activeThreads", 100) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("activeThreads", 100) + )) + .build(), + true); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("totalThreads", 5, 20, 25) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("totalThreads", 5, 20, 25) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -948,38 +1181,49 @@ public void test_ScalarOverVector_Div_Long_Long() throws Exception { @Test public void test_ScalarOverVector_Div_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - DoubleColumn.of("activeThreads", 10) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + DoubleColumn.of("activeThreads", 10) + )) + .build(), + true); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("totalThreads", 5, 20, 25) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("totalThreads", 5, 20, 25) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -999,38 +1243,49 @@ public void test_ScalarOverVector_Div_Long_Double() throws Exception { @Test public void test_ScalarOverVector_Div_Double_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - DoubleColumn.of("activeThreads", 10) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("activeThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + DoubleColumn.of("activeThreads", 10) + )) + .build(), + true); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("totalThreads", 5, 20, 25) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("totalThreads", 5, 20, 25) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1050,36 +1305,49 @@ public void test_ScalarOverVector_Div_Double_Long() throws Exception { @Test public void test_VectorOverScalar_Add_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 5, 20, 25) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 5, 20, 25) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 5) + )) + .build(), + true); } + throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1099,36 +1367,48 @@ public void test_VectorOverScalar_Add_Long_Long() throws Exception { @Test public void test_VectorOverScalar_Add_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 5, 20, 25) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 5, 20, 25) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - DoubleColumn.of("totalThreads", 5.7) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + DoubleColumn.of("totalThreads", 5.7) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1148,36 +1428,48 @@ public void test_VectorOverScalar_Add_Long_Double() throws Exception { @Test public void test_VectorOverScalar_Add_Double_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 5.5, 20.6, 25.7) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 5.5, 20.6, 25.7) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 5) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1197,36 +1489,48 @@ public void test_VectorOverScalar_Add_Double_Long() throws Exception { @Test public void test_VectorOverScalar_Sub_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 3, 4, 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 3, 4, 5) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 5) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1246,36 +1550,48 @@ public void test_VectorOverScalar_Sub_Long_Long() throws Exception { @Test public void test_VectorOverScalar_Sub_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 3, 4, 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 3, 4, 5) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - DoubleColumn.of("totalThreads", 5.5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + DoubleColumn.of("totalThreads", 5.5) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1295,36 +1611,48 @@ public void test_VectorOverScalar_Sub_Long_Double() throws Exception { @Test public void test_VectorOverScalar_Sub_Double_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 3.5, 4.5, 5.5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 3.5, 4.5, 5.5) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 5) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1344,36 +1672,48 @@ public void test_VectorOverScalar_Sub_Double_Long() throws Exception { @Test public void test_VectorOverScalar_Mul_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 3, 4, 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 3, 4, 5) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 3) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1393,36 +1733,48 @@ public void test_VectorOverScalar_Mul_Long_Long() throws Exception { @Test public void test_VectorOverScalar_Mul_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 3, 4, 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 3, 4, 5) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - DoubleColumn.of("totalThreads", 3.5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + DoubleColumn.of("totalThreads", 3.5) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1442,36 +1794,48 @@ public void test_VectorOverScalar_Mul_Long_Double() throws Exception { @Test public void test_VectorOverScalar_Mul_Double_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 3.5, 4.5, 5.5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 3.5, 4.5, 5.5) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 3) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1491,37 +1855,49 @@ public void test_VectorOverScalar_Mul_Double_Long() throws Exception { @Test public void test_VectorOverScalar_Div_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 55, 60, 77) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 55, 60, 77) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 11) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 11) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1541,37 +1917,49 @@ public void test_VectorOverScalar_Div_Long_Long() throws Exception { @Test public void test_VectorOverScalar_Div_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - LongColumn.of("activeThreads", 20, 25, 50) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + LongColumn.of("activeThreads", 20, 25, 50) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - DoubleColumn.of("totalThreads", 50.0) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + DoubleColumn.of("totalThreads", 50.0) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1591,37 +1979,49 @@ public void test_VectorOverScalar_Div_Long_Double() throws Exception { @Test public void test_VectorOverScalar_Div_Double_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest request = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - String metric = request.getFields().get(0).getName(); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 20, 25, 50) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 20, 25, 50) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1), - LongColumn.of("totalThreads", 50) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp")) + .valColumns(List.of("totalThreads")) + .rows(1) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1), + LongColumn.of("totalThreads", 50) + )) + .build(), + true); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1641,39 +2041,50 @@ public void test_VectorOverScalar_Div_Double_Long() throws Exception { @Test public void test_VectorOverVector_Add_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - LongColumn.of("activeThreads", 1, 5, 9) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + LongColumn.of("activeThreads", 1, 5, 9) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - LongColumn.of("totalThreads", 21, 32, 43) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + LongColumn.of("totalThreads", 21, 32, 43) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1692,39 +2103,50 @@ public void test_VectorOverVector_Add_Long_Long() throws Exception { @Test public void test_VectorOverVector_Add_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - LongColumn.of("activeThreads", 1, 5, 9) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + LongColumn.of("activeThreads", 1, 5, 9) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - DoubleColumn.of("totalThreads", 21.5, 32.6, 43.7) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + DoubleColumn.of("totalThreads", 21.5, 32.6, 43.7) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1743,40 +2165,51 @@ public void test_VectorOverVector_Add_Long_Double() throws Exception { @Test public void test_VectorOverVector_Add_Double_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - DoubleColumn.of("activeThreads", 1.1, 5.2, 9.3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + DoubleColumn.of("activeThreads", 1.1, 5.2, 9.3) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - DoubleColumn.of("totalThreads", 21.5, 32.6, 43.7) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + DoubleColumn.of("totalThreads", 21.5, 32.6, 43.7) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1795,39 +2228,50 @@ public void test_VectorOverVector_Add_Double_Double() throws Exception { @Test public void test_VectorOverVector_Sub_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - LongColumn.of("activeThreads", 1, 5, 9) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + LongColumn.of("activeThreads", 1, 5, 9) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - LongColumn.of("totalThreads", 21, 32, 43) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + LongColumn.of("totalThreads", 21, 32, 43) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1846,39 +2290,50 @@ public void test_VectorOverVector_Sub_Long_Long() throws Exception { @Test public void test_VectorOverVector_Sub_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - LongColumn.of("activeThreads", 1, 5, 9) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + LongColumn.of("activeThreads", 1, 5, 9) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - DoubleColumn.of("totalThreads", 21.1, 32.2, 43.3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + DoubleColumn.of("totalThreads", 21.1, 32.2, 43.3) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1897,39 +2352,50 @@ public void test_VectorOverVector_Sub_Long_Double() throws Exception { @Test public void test_VectorOverVector_Sub_Double_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - DoubleColumn.of("activeThreads", 1.1, 5.5, 9.9) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + DoubleColumn.of("activeThreads", 1.1, 5.5, 9.9) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - LongColumn.of("totalThreads", 21, 32, 43) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + LongColumn.of("totalThreads", 21, 32, 43) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1948,39 +2414,50 @@ public void test_VectorOverVector_Sub_Double_Long() throws Exception { @Test public void test_VectorOverVector_Sub_Double_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - DoubleColumn.of("activeThreads", 1.4, 5.5, 9.6) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + DoubleColumn.of("activeThreads", 1.4, 5.5, 9.6) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - DoubleColumn.of("totalThreads", 21.1, 32.2, 43.3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + DoubleColumn.of("totalThreads", 21.1, 32.2, 43.3) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1999,39 +2476,50 @@ public void test_VectorOverVector_Sub_Double_Double() throws Exception { @Test public void test_VectorOverVector_Mul_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - LongColumn.of("activeThreads", 1, 5, 9) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + LongColumn.of("activeThreads", 1, 5, 9) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - LongColumn.of("totalThreads", 21, 32, 43) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + LongColumn.of("totalThreads", 21, 32, 43) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2050,39 +2538,50 @@ public void test_VectorOverVector_Mul_Long_Long() throws Exception { @Test public void test_VectorOverVector_Mul_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - LongColumn.of("activeThreads", 1, 5, 9) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + LongColumn.of("activeThreads", 1, 5, 9) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - DoubleColumn.of("totalThreads", 21.2, 32.3, 43.3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + DoubleColumn.of("totalThreads", 21.2, 32.3, 43.3) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2101,39 +2600,50 @@ public void test_VectorOverVector_Mul_Long_Double() throws Exception { @Test public void test_VectorOverVector_Mul_Double_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - DoubleColumn.of("activeThreads", 1.1, 5.2, 9.3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + DoubleColumn.of("activeThreads", 1.1, 5.2, 9.3) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - LongColumn.of("totalThreads", 21, 32, 43) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + LongColumn.of("totalThreads", 21, 32, 43) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2152,40 +2662,51 @@ public void test_VectorOverVector_Mul_Double_Long() throws Exception { @Test public void test_VectorOverVector_Mul_Double_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - DoubleColumn.of("activeThreads", 1.1, 5.2, 9.3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + DoubleColumn.of("activeThreads", 1.1, 5.2, 9.3) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - DoubleColumn.of("totalThreads", 21.1, 32.2, 43.3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + DoubleColumn.of("totalThreads", 21.1, 32.2, 43.3) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2204,39 +2725,50 @@ public void test_VectorOverVector_Mul_Double_Double() throws Exception { @Test public void test_VectorOverVector_Div_Long_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - LongColumn.of("activeThreads", 50, 100, 200) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + LongColumn.of("activeThreads", 50, 100, 200) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - LongColumn.of("totalThreads", 25, 50, 100) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + LongColumn.of("totalThreads", 25, 50, 100) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2255,39 +2787,50 @@ public void test_VectorOverVector_Div_Long_Long() throws Exception { @Test public void test_VectorOverVector_Div_Long_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - LongColumn.of("activeThreads", 50, 100, 200) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + LongColumn.of("activeThreads", 50, 100, 200) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - DoubleColumn.of("totalThreads", 25.5, 50.6, 100.6) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + DoubleColumn.of("totalThreads", 25.5, 50.6, 100.6) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2306,39 +2849,50 @@ public void test_VectorOverVector_Div_Long_Double() throws Exception { @Test public void test_VectorOverVector_Div_Double_Long() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - DoubleColumn.of("activeThreads", 12, 25, 200) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + DoubleColumn.of("activeThreads", 12, 25, 200) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - LongColumn.of("totalThreads", 25, 50, 100) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + LongColumn.of("totalThreads", 25, 50, 100) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2357,39 +2911,50 @@ public void test_VectorOverVector_Div_Double_Long() throws Exception { @Test public void test_VectorOverVector_Div_Double_Double() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app1"), - DoubleColumn.of("activeThreads", 12.1, 25.2, 200.3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app1"), + DoubleColumn.of("activeThreads", 12.1, 25.2, 200.3) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app2", "app3", "app4"), - DoubleColumn.of("totalThreads", 25.6, 50.7, 100.8) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app2", "app3", "app4"), + DoubleColumn.of("totalThreads", 25.6, 50.7, 100.8) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2408,39 +2973,50 @@ public void test_VectorOverVector_Div_Double_Double() throws Exception { @Test public void test_VectorOverVector_NoIntersection() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 12.1, 25.2, 200.3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 12.1, 25.2, 200.3) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app4", "app5", "app6"), - DoubleColumn.of("totalThreads", 25.6, 50.7, 100.8) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app4", "app5", "app6"), + DoubleColumn.of("totalThreads", 25.6, 50.7, 100.8) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2459,47 +3035,64 @@ public void test_VectorOverVector_NoIntersection() throws Exception { */ @Test public void test_VectorOverVector_NoIntersection_2() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 12.1, 25.2, 200.3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 12.1, 25.2, 200.3) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 4, 5, 6), - StringColumn.of("appName", "app4", "app5", "app6"), - DoubleColumn.of("totalThreads", 25.6, 50.7, 100.8) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 4, 5, 6), + StringColumn.of("appName", "app4", "app5", "app6"), + DoubleColumn.of("totalThreads", 25.6, 50.7, 100.8) + )) + .build(), + false); } if ("newThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 6, 7), - StringColumn.of("appName", "app6", "app7"), - DoubleColumn.of("newThreads", 106, 107) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("newThreads")) + .rows(2) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 6, 7), + StringColumn.of("appName", "app6", "app7"), + DoubleColumn.of("newThreads", 106, 107) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -2520,47 +3113,64 @@ public void test_VectorOverVector_NoIntersection_2() throws Exception { */ @Test public void test_VectorOverVector_NoIntersection_3() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 12.1, 25.2, 200.3) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 12.1, 25.2, 200.3) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 3, 4), - StringColumn.of("appName", "app3", "app4"), - DoubleColumn.of("totalThreads", 25.6, 50.7) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(2) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 3, 4), + StringColumn.of("appName", "app3", "app4"), + DoubleColumn.of("totalThreads", 25.6, 50.7) + )) + .build(), + false); } if ("newThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 6, 7), - StringColumn.of("appName", "app6", "app7"), - DoubleColumn.of("newThreads", 106, 107) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("newThreads")) + .rows(2) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 6, 7), + StringColumn.of("appName", "app6", "app7"), + DoubleColumn.of("newThreads", 106, 107) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -2577,46 +3187,63 @@ public void test_VectorOverVector_NoIntersection_3() throws Exception { @Test public void test_MultipleExpressions() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - String metric = answer.getArgument(0, QueryRequest.class) - .getFields() - .get(0).getName(); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); + String metric = tableScan.selectorList().get(0).getOutputName(); if ("activeThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 3, 4, 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 3, 4, 5) + )) + .build(), + false); } if ("totalThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 2, 3, 4), - StringColumn.of("appName", "app2", "app3", "app4"), - DoubleColumn.of("totalThreads", 25, 26, 27) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("totalThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 2, 3, 4), + StringColumn.of("appName", "app2", "app3", "app4"), + DoubleColumn.of("totalThreads", 25, 26, 27) + )) + .build(), + false); } if ("newThreads".equals(metric)) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 3, 4, 5), - StringColumn.of("appName", "app3", "app4", "app5"), - DoubleColumn.of("newThreads", 35, 36, 37) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("newThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 3, 4, 5), + StringColumn.of("appName", "app3", "app4", "app5"), + DoubleColumn.of("newThreads", 35, 36, 37) + )) + .build(), + false); } throw new IllegalArgumentException("Invalid metric: " + metric); }); IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + "/ " + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + "* " @@ -2693,13 +3320,19 @@ public void test_RelativeComparison() throws Exception { @Test public void test_FilterStep_Double_GT() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 3, 4, 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 3, 4, 5) + )) + .build(), + false); }); // @@ -2707,14 +3340,14 @@ public void test_FilterStep_Double_GT() throws Exception { // { IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 2"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 2"); PipelineQueryResult response = evaluator.execute().get(); // all 3 records satisfy the filter condition @@ -2735,14 +3368,14 @@ public void test_FilterStep_Double_GT() throws Exception { // { IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 3"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 3"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(2, response.getRows()); @@ -2759,14 +3392,14 @@ public void test_FilterStep_Double_GT() throws Exception { // { IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 4"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 4"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(1, response.getRows()); @@ -2783,14 +3416,14 @@ public void test_FilterStep_Double_GT() throws Exception { // { IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 5"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(0, response.getRows()); @@ -2803,13 +3436,19 @@ public void test_FilterStep_Double_GT() throws Exception { @Test public void test_FilterStep_Double_GTE() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 3, 4, 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 3, 4, 5) + )) + .build(), + false); }); // @@ -2817,13 +3456,13 @@ public void test_FilterStep_Double_GTE() throws Exception { // { IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 2"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 2"); PipelineQueryResult response = evaluator.execute().get(); // all 3 records satisfy the filter condition @@ -2842,13 +3481,13 @@ public void test_FilterStep_Double_GTE() throws Exception { // { IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 3"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 3"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(3, response.getRows()); @@ -2866,14 +3505,14 @@ public void test_FilterStep_Double_GTE() throws Exception { // { IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 4"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 4"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(2, response.getRows()); @@ -2890,14 +3529,14 @@ public void test_FilterStep_Double_GTE() throws Exception { // { IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 5"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(1, response.getRows()); @@ -2913,13 +3552,13 @@ public void test_FilterStep_Double_GTE() throws Exception { // { IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 6"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 6"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(0, response.getRows()); From a2f8720b7af53b90accc5ac12550f323cc6faa74 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Thu, 12 Jun 2025 16:29:23 +0800 Subject: [PATCH 19/22] Relocate planner --- .../metric/expression/api/MetricQueryApi.java | 20 +- ...va => MetricExpressionLogicalPlanner.java} | 6 +- .../MetricExpressionPhysicalPlanner.java} | 35 +- .../MetricExpressionPlannerSettings.java} | 6 +- .../MetricExpressionPhysicalPlannerTest.java} | 864 +++++++++--------- .../builder/PhysicalPlannerTest.java | 66 +- 6 files changed, 490 insertions(+), 507 deletions(-) rename server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/{LogicalPlanBuilder.java => MetricExpressionLogicalPlanner.java} (95%) rename server/metric-expression/src/main/java/org/bithon/server/metric/expression/{pipeline/PhysicalPlanner.java => plan/MetricExpressionPhysicalPlanner.java} (93%) rename server/metric-expression/src/main/java/org/bithon/server/metric/expression/{pipeline/QueryPipelineBuilderSettings.java => plan/MetricExpressionPlannerSettings.java} (80%) rename server/metric-expression/src/test/java/org/bithon/server/metric/expression/{pipeline/step/ArithmeticStepTest.java => plan/MetricExpressionPhysicalPlannerTest.java} (82%) diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java index 047bb03748..c017447c2e 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java @@ -28,9 +28,9 @@ import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.result.Column; import org.bithon.server.datasource.query.result.PipelineQueryResult; -import org.bithon.server.metric.expression.pipeline.PhysicalPlanner; +import org.bithon.server.metric.expression.plan.MetricExpressionPhysicalPlanner; +import org.bithon.server.storage.datasource.SchemaManager; import org.bithon.server.web.service.WebServiceModuleEnabler; -import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; import org.bithon.server.web.service.datasource.api.QueryResponse; import org.bithon.server.web.service.datasource.api.TimeSeriesMetric; @@ -56,10 +56,10 @@ @RestController @Conditional(WebServiceModuleEnabler.class) public class MetricQueryApi { - private final IDataSourceApi dataSourceApi; + private final SchemaManager schemaManager; - public MetricQueryApi(IDataSourceApi dataSourceApi) { - this.dataSourceApi = dataSourceApi; + public MetricQueryApi(SchemaManager schemaManager) { + this.schemaManager = schemaManager; } @Data @@ -87,11 +87,11 @@ public static class MetricQueryRequest { @Experimental @PostMapping("/api/metric/timeseries") public QueryResponse timeSeries(@Validated @RequestBody MetricQueryRequest request) throws Exception { - IPhysicalPlan pipeline = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) - .interval(request.getInterval()) - .condition(request.getCondition()) - .build(request.getExpression()); + IPhysicalPlan pipeline = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaManager) + .interval(request.getInterval()) + .condition(request.getCondition()) + .build(request.getExpression()); Duration step = request.getInterval().calculateStep(); TimeSpan start = request.getInterval().getStartISO8601(); diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionLogicalPlanner.java similarity index 95% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java rename to server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionLogicalPlanner.java index 32366d4144..6a52016c30 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/LogicalPlanBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionLogicalPlanner.java @@ -45,10 +45,10 @@ * @author frank.chen021@outlook.com * @date 2025/6/4 23:43 */ -public class LogicalPlanBuilder implements IMetricExpressionVisitor { +public class MetricExpressionLogicalPlanner implements IMetricExpressionVisitor { private final ISchemaProvider schemaProvider; - public LogicalPlanBuilder(ISchemaProvider schemaProvider) { + public MetricExpressionLogicalPlanner(ISchemaProvider schemaProvider) { this.schemaProvider = schemaProvider; } @@ -101,7 +101,7 @@ public ILogicalPlan visit(ArithmeticExpression expression) { @Override public ILogicalPlan visit(ConditionalExpression expression) { - PredicateEnum predicate = switch(expression.getType()) { + PredicateEnum predicate = switch (expression.getType()) { case "<" -> PredicateEnum.LT; case "<=" -> PredicateEnum.LTE; case ">" -> PredicateEnum.GT; diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlanner.java similarity index 93% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java rename to server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlanner.java index 7f086d833a..6e670795e5 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/PhysicalPlanner.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlanner.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.metric.expression.pipeline; +package org.bithon.server.metric.expression.plan; import org.bithon.component.commons.expression.ArithmeticExpression; @@ -45,8 +45,6 @@ import org.bithon.server.metric.expression.ast.MetricExpressionASTBuilder; import org.bithon.server.metric.expression.ast.MetricExpressionOptimizer; import org.bithon.server.metric.expression.ast.PredicateEnum; -import org.bithon.server.metric.expression.plan.LogicalPlanBuilder; -import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; import org.bithon.server.web.service.datasource.api.QueryField; @@ -58,39 +56,33 @@ * @author frank.chen021@outlook.com * @date 4/4/25 3:53 pm */ -public class PhysicalPlanner { +public class MetricExpressionPhysicalPlanner { - private IDataSourceApi dataSourceApi; private IntervalRequest intervalRequest; private String condition; - private QueryPipelineBuilderSettings settings = QueryPipelineBuilderSettings.DEFAULT; + private MetricExpressionPlannerSettings settings = MetricExpressionPlannerSettings.DEFAULT; private ISchemaProvider schemaProvider; - public static PhysicalPlanner builder() { - return new PhysicalPlanner(); + public static MetricExpressionPhysicalPlanner builder() { + return new MetricExpressionPhysicalPlanner(); } - public PhysicalPlanner dataSourceApi(IDataSourceApi dataSourceApi) { - this.dataSourceApi = dataSourceApi; - return this; - } - - public PhysicalPlanner interval(IntervalRequest intervalRequest) { + public MetricExpressionPhysicalPlanner interval(IntervalRequest intervalRequest) { this.intervalRequest = intervalRequest; return this; } - public PhysicalPlanner condition(String condition) { + public MetricExpressionPhysicalPlanner condition(String condition) { this.condition = condition; return this; } - public PhysicalPlanner settings(QueryPipelineBuilderSettings settings) { + public MetricExpressionPhysicalPlanner settings(MetricExpressionPlannerSettings settings) { this.settings = settings; return this; } - public PhysicalPlanner schemaProvider(ISchemaProvider schemaProvider) { + public MetricExpressionPhysicalPlanner schemaProvider(ISchemaProvider schemaProvider) { this.schemaProvider = schemaProvider; return this; } @@ -112,7 +104,7 @@ public IPhysicalPlan timeSeries(String expression) { // The optimization is applied here so that the above parse can be tested separately expr = MetricExpressionOptimizer.optimize(expr); - ILogicalPlan logicalPlan = expr.accept(new LogicalPlanBuilder(schemaProvider)); + ILogicalPlan logicalPlan = expr.accept(new MetricExpressionLogicalPlanner(schemaProvider)); return logicalPlan.accept(new PhysicalPlanImpl(Interval.of(this.intervalRequest.getStartISO8601(), this.intervalRequest.getEndISO8601(), this.intervalRequest.calculateStep(), @@ -126,7 +118,7 @@ public IPhysicalPlan groupBy(String expression) { // The optimization is applied here so that the above parse can be tested separately expr = MetricExpressionOptimizer.optimize(expr); - ILogicalPlan logicalPlan = expr.accept(new LogicalPlanBuilder(schemaProvider)); + ILogicalPlan logicalPlan = expr.accept(new MetricExpressionLogicalPlanner(schemaProvider)); return logicalPlan.accept(new PhysicalPlanImpl(Interval.of(this.intervalRequest.getStartISO8601(), this.intervalRequest.getEndISO8601(), null, @@ -182,7 +174,7 @@ public IPhysicalPlan visitScalar(LogicalScalar scalar) { @Override public IPhysicalPlan visitFilter(LogicalFilter filter) { - switch(filter.op()) { + switch (filter.op()) { case LT: return new FilterStep.LT(filter.left().accept(this), (Number) ((LogicalScalar) filter.right()).literal().getValue()); @@ -317,8 +309,7 @@ public IPhysicalPlan visit(ArithmeticExpression expression) { case "-" -> new ArithmeticStep.Sub(expression.getLhs().accept(this), expression.getRhs().accept(this)); case "*" -> new ArithmeticStep.Mul(expression.getLhs().accept(this), expression.getRhs().accept(this)); case "/" -> new ArithmeticStep.Div(expression.getLhs().accept(this), expression.getRhs().accept(this)); - default -> - throw new UnsupportedOperationException("Unsupported arithmetic expression: " + expression.getType()); + default -> throw new UnsupportedOperationException("Unsupported arithmetic expression: " + expression.getType()); }; } diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilderSettings.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionPlannerSettings.java similarity index 80% rename from server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilderSettings.java rename to server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionPlannerSettings.java index 46d59fa83f..a4e223e75e 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/pipeline/QueryPipelineBuilderSettings.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionPlannerSettings.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bithon.server.metric.expression.pipeline; +package org.bithon.server.metric.expression.plan; import lombok.Data; @@ -23,8 +23,8 @@ * @date 2025/6/4 21:47 */ @Data -public class QueryPipelineBuilderSettings { - public static final QueryPipelineBuilderSettings DEFAULT = new QueryPipelineBuilderSettings(); +public class MetricExpressionPlannerSettings { + public static final MetricExpressionPlannerSettings DEFAULT = new MetricExpressionPlannerSettings(); private boolean pushdownPostFilter = false; private boolean pushdownArithmetic = false; diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlannerTest.java similarity index 82% rename from server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java rename to server/metric-expression/src/test/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlannerTest.java index 25af413aef..ea232d09cc 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/pipeline/step/ArithmeticStepTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlannerTest.java @@ -14,8 +14,7 @@ * limitations under the License. */ -package org.bithon.server.metric.expression.pipeline.step; - +package org.bithon.server.metric.expression.plan; import org.bithon.component.commons.utils.HumanReadableDuration; import org.bithon.component.commons.utils.HumanReadableNumber; @@ -38,7 +37,6 @@ import org.bithon.server.datasource.query.result.PipelineQueryResult; import org.bithon.server.datasource.query.result.StringColumn; import org.bithon.server.datasource.store.IDataStoreSpec; -import org.bithon.server.metric.expression.pipeline.PhysicalPlanner; import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; import org.bithon.server.web.service.datasource.api.QueryRequest; @@ -56,7 +54,7 @@ * @date 4/4/25 9:27 pm */ @SuppressWarnings("PointlessArithmeticExpression") -public class ArithmeticStepTest { +public class MetricExpressionPhysicalPlannerTest { private ISchema schema; private ISchemaProvider schemaProvider; @@ -146,7 +144,7 @@ public CompletableFuture execute() { } @Test - public void test_ScalarOverLiteral_Add_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Add_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -156,15 +154,14 @@ public void test_ScalarOverLiteral_Add_Long_Long() throws Exception { LongColumn.of("activeThreads", 1))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -172,7 +169,7 @@ public void test_ScalarOverLiteral_Add_Long_Long() throws Exception { } @Test - public void test_ScalarOverLiteral_Add_Long_Double() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Add_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -182,14 +179,14 @@ public void test_ScalarOverLiteral_Add_Long_Double() throws Exception { LongColumn.of("activeThreads", 1))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 3.3"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 3.3"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -197,7 +194,7 @@ public void test_ScalarOverLiteral_Add_Long_Double() throws Exception { } @Test - public void test_ScalarOverLiteral_Add_Double_Long() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Add_Double_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -207,14 +204,14 @@ public void test_ScalarOverLiteral_Add_Double_Long() throws Exception { DoubleColumn.of("activeThreads", 3.7))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -222,7 +219,7 @@ public void test_ScalarOverLiteral_Add_Double_Long() throws Exception { } @Test - public void test_ScalarOverLiteral_Add_Double_Double() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Add_Double_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -232,14 +229,14 @@ public void test_ScalarOverLiteral_Add_Double_Double() throws Exception { DoubleColumn.of("activeThreads", 10.5))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 2.2"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 2.2"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -247,7 +244,7 @@ public void test_ScalarOverLiteral_Add_Double_Double() throws Exception { } @Test - public void test_ScalarOverSizeLiteral_Add_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverSizeLiteral_Add_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -257,14 +254,14 @@ public void test_ScalarOverSizeLiteral_Add_Long_Long() throws Exception { LongColumn.of("activeThreads", 1))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5Mi"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5Mi"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -272,7 +269,7 @@ public void test_ScalarOverSizeLiteral_Add_Long_Long() throws Exception { } @Test - public void test_ScalarOverPercentageLiteral_Add_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverPercentageLiteral_Add_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -282,14 +279,14 @@ public void test_ScalarOverPercentageLiteral_Add_Long_Long() throws Exception { LongColumn.of("activeThreads", 1))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 90%"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 90%"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -297,7 +294,7 @@ public void test_ScalarOverPercentageLiteral_Add_Long_Long() throws Exception { } @Test - public void test_ScalarOverDurationLiteral_Add_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverDurationLiteral_Add_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -307,14 +304,14 @@ public void test_ScalarOverDurationLiteral_Add_Long_Long() throws Exception { LongColumn.of("activeThreads", 1))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 1h"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 1h"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -322,7 +319,7 @@ public void test_ScalarOverDurationLiteral_Add_Long_Long() throws Exception { } @Test - public void test_ScalarOverLiteral_Sub_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Sub_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -332,14 +329,14 @@ public void test_ScalarOverLiteral_Sub_Long_Long() throws Exception { LongColumn.of("activeThreads", 1))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -347,7 +344,7 @@ public void test_ScalarOverLiteral_Sub_Long_Long() throws Exception { } @Test - public void test_ScalarOverLiteral_Sub_Double_Double() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Sub_Double_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -357,14 +354,14 @@ public void test_ScalarOverLiteral_Sub_Double_Double() throws Exception { DoubleColumn.of("activeThreads", 10.5))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 2.2"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 2.2"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -372,7 +369,7 @@ public void test_ScalarOverLiteral_Sub_Double_Double() throws Exception { } @Test - public void test_ScalarOverLiteral_Mul_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Mul_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -382,14 +379,14 @@ public void test_ScalarOverLiteral_Mul_Long_Long() throws Exception { LongColumn.of("activeThreads", 1))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -397,7 +394,7 @@ public void test_ScalarOverLiteral_Mul_Long_Long() throws Exception { } @Test - public void test_ScalarOverLiteral_Mul_Double_Long() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Mul_Double_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -407,14 +404,14 @@ public void test_ScalarOverLiteral_Mul_Double_Long() throws Exception { DoubleColumn.of("activeThreads", 5.5))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -422,7 +419,7 @@ public void test_ScalarOverLiteral_Mul_Double_Long() throws Exception { } @Test - public void test_ScalarOverLiteral_Mul_Long_Double() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Mul_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -432,14 +429,14 @@ public void test_ScalarOverLiteral_Mul_Long_Double() throws Exception { LongColumn.of("activeThreads", 1))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5.5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5.5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -447,7 +444,7 @@ public void test_ScalarOverLiteral_Mul_Long_Double() throws Exception { } @Test - public void test_ScalarOverLiteral_Mul_Double_Double() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Mul_Double_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -457,14 +454,14 @@ public void test_ScalarOverLiteral_Mul_Double_Double() throws Exception { DoubleColumn.of("activeThreads", 3.5))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 3"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 3"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -472,7 +469,7 @@ public void test_ScalarOverLiteral_Mul_Double_Double() throws Exception { } @Test - public void test_ScalarOverLiteral_Div_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Div_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -482,14 +479,14 @@ public void test_ScalarOverLiteral_Div_Long_Long() throws Exception { LongColumn.of("activeThreads", 10))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 5"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 5"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -497,7 +494,7 @@ public void test_ScalarOverLiteral_Div_Long_Long() throws Exception { } @Test - public void test_ScalarOverLiteral_Div_Double_Long() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Div_Double_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -507,14 +504,14 @@ public void test_ScalarOverLiteral_Div_Double_Long() throws Exception { DoubleColumn.of("activeThreads", 10))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -522,7 +519,7 @@ public void test_ScalarOverLiteral_Div_Double_Long() throws Exception { } @Test - public void test_ScalarOverLiteral_Div_Long_Double() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Div_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -532,14 +529,14 @@ public void test_ScalarOverLiteral_Div_Long_Double() throws Exception { LongColumn.of("activeThreads", 10))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20.0"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20.0"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -547,7 +544,7 @@ public void test_ScalarOverLiteral_Div_Long_Double() throws Exception { } @Test - public void test_ScalarOverLiteral_Div_Double_Double() throws Exception { + public void test_Arithmetic_ScalarOverLiteral_Div_Double_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp")) @@ -557,14 +554,14 @@ public void test_ScalarOverLiteral_Div_Double_Double() throws Exception { DoubleColumn.of("activeThreads", 10.5))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 3.0"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 3.0"); PipelineQueryResult response = evaluator.execute().get(); Column valueCol = response.getTable().getColumn("activeThreads"); @@ -572,7 +569,7 @@ public void test_ScalarOverLiteral_Div_Double_Double() throws Exception { } @Test - public void test_VectorOverLiteral_Add_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverLiteral_Add_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp", "appName")) @@ -584,15 +581,15 @@ public void test_VectorOverLiteral_Add_Long_Long() throws Exception { LongColumn.of("activeThreads", 5, 20, 25))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "5"); PipelineQueryResult response = evaluator.execute().get(); @@ -611,7 +608,7 @@ public void test_VectorOverLiteral_Add_Long_Long() throws Exception { } @Test - public void test_VectorOverLiteral_Sub_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverLiteral_Sub_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp", "appName")) @@ -623,15 +620,15 @@ public void test_VectorOverLiteral_Sub_Long_Long() throws Exception { LongColumn.of("activeThreads", 5, 20, 25))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + " 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -650,7 +647,7 @@ public void test_VectorOverLiteral_Sub_Long_Long() throws Exception { } @Test - public void test_VectorOverLiteral_Mul_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverLiteral_Mul_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp", "appName")) @@ -662,15 +659,15 @@ public void test_VectorOverLiteral_Mul_Long_Long() throws Exception { LongColumn.of("activeThreads", 5, 20, 25))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "5"); PipelineQueryResult response = evaluator.execute().get(); @@ -689,7 +686,7 @@ public void test_VectorOverLiteral_Mul_Long_Long() throws Exception { } @Test - public void test_VectorOverLiteral_Div_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverLiteral_Div_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenReturn(new MockReadStep(PipelineQueryResult.builder() .keyColumns(List.of("_timestamp", "appName")) @@ -701,15 +698,15 @@ public void test_VectorOverLiteral_Div_Long_Long() throws Exception { LongColumn.of("activeThreads", 5, 24, 25))) .build())); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "5"); PipelineQueryResult response = evaluator.execute().get(); @@ -728,7 +725,7 @@ public void test_VectorOverLiteral_Div_Long_Long() throws Exception { } @Test - public void test_ScalarOverScalar_Add_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverScalar_Add_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -761,15 +758,15 @@ public void test_ScalarOverScalar_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -779,7 +776,7 @@ public void test_ScalarOverScalar_Add_Long_Long() throws Exception { } @Test - public void test_ScalarOverScalar_Sub_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverScalar_Sub_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -813,15 +810,15 @@ public void test_ScalarOverScalar_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -831,7 +828,7 @@ public void test_ScalarOverScalar_Sub_Long_Long() throws Exception { } @Test - public void test_ScalarOverScalar_Mul_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverScalar_Mul_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -864,15 +861,15 @@ public void test_ScalarOverScalar_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -882,7 +879,7 @@ public void test_ScalarOverScalar_Mul_Long_Long() throws Exception { } @Test - public void test_ScalarOverScalar_Div_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverScalar_Div_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -915,15 +912,15 @@ public void test_ScalarOverScalar_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -933,7 +930,7 @@ public void test_ScalarOverScalar_Div_Long_Long() throws Exception { } @Test - public void test_ScalarOverVector_Add_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverVector_Add_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -967,15 +964,15 @@ public void test_ScalarOverVector_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -994,7 +991,7 @@ public void test_ScalarOverVector_Add_Long_Long() throws Exception { } @Test - public void test_ScalarOverVector_Sub_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverVector_Sub_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1029,15 +1026,15 @@ public void test_ScalarOverVector_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1056,7 +1053,7 @@ public void test_ScalarOverVector_Sub_Long_Long() throws Exception { } @Test - public void test_ScalarOverVector_Mul_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverVector_Mul_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1091,15 +1088,15 @@ public void test_ScalarOverVector_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1118,7 +1115,7 @@ public void test_ScalarOverVector_Mul_Long_Long() throws Exception { } @Test - public void test_ScalarOverVector_Div_Long_Long() throws Exception { + public void test_Arithmetic_ScalarOverVector_Div_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1153,15 +1150,15 @@ public void test_ScalarOverVector_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1180,7 +1177,7 @@ public void test_ScalarOverVector_Div_Long_Long() throws Exception { } @Test - public void test_ScalarOverVector_Div_Long_Double() throws Exception { + public void test_Arithmetic_ScalarOverVector_Div_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1215,15 +1212,15 @@ public void test_ScalarOverVector_Div_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1242,7 +1239,7 @@ public void test_ScalarOverVector_Div_Long_Double() throws Exception { } @Test - public void test_ScalarOverVector_Div_Double_Long() throws Exception { + public void test_Arithmetic_ScalarOverVector_Div_Double_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1277,15 +1274,15 @@ public void test_ScalarOverVector_Div_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -1304,7 +1301,7 @@ public void test_ScalarOverVector_Div_Double_Long() throws Exception { } @Test - public void test_VectorOverScalar_Add_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverScalar_Add_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1339,15 +1336,15 @@ public void test_VectorOverScalar_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1366,7 +1363,7 @@ public void test_VectorOverScalar_Add_Long_Long() throws Exception { } @Test - public void test_VectorOverScalar_Add_Long_Double() throws Exception { + public void test_Arithmetic_VectorOverScalar_Add_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1400,15 +1397,15 @@ public void test_VectorOverScalar_Add_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1427,7 +1424,7 @@ public void test_VectorOverScalar_Add_Long_Double() throws Exception { } @Test - public void test_VectorOverScalar_Add_Double_Long() throws Exception { + public void test_Arithmetic_VectorOverScalar_Add_Double_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1461,15 +1458,15 @@ public void test_VectorOverScalar_Add_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1488,7 +1485,7 @@ public void test_VectorOverScalar_Add_Double_Long() throws Exception { } @Test - public void test_VectorOverScalar_Sub_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverScalar_Sub_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1522,15 +1519,15 @@ public void test_VectorOverScalar_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1549,7 +1546,7 @@ public void test_VectorOverScalar_Sub_Long_Long() throws Exception { } @Test - public void test_VectorOverScalar_Sub_Long_Double() throws Exception { + public void test_Arithmetic_VectorOverScalar_Sub_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1583,15 +1580,15 @@ public void test_VectorOverScalar_Sub_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1610,7 +1607,7 @@ public void test_VectorOverScalar_Sub_Long_Double() throws Exception { } @Test - public void test_VectorOverScalar_Sub_Double_Long() throws Exception { + public void test_Arithmetic_VectorOverScalar_Sub_Double_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1644,15 +1641,15 @@ public void test_VectorOverScalar_Sub_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1671,7 +1668,7 @@ public void test_VectorOverScalar_Sub_Double_Long() throws Exception { } @Test - public void test_VectorOverScalar_Mul_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverScalar_Mul_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1705,15 +1702,15 @@ public void test_VectorOverScalar_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1732,7 +1729,7 @@ public void test_VectorOverScalar_Mul_Long_Long() throws Exception { } @Test - public void test_VectorOverScalar_Mul_Long_Double() throws Exception { + public void test_Arithmetic_VectorOverScalar_Mul_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1766,15 +1763,15 @@ public void test_VectorOverScalar_Mul_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1793,7 +1790,7 @@ public void test_VectorOverScalar_Mul_Long_Double() throws Exception { } @Test - public void test_VectorOverScalar_Mul_Double_Long() throws Exception { + public void test_Arithmetic_VectorOverScalar_Mul_Double_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1827,15 +1824,15 @@ public void test_VectorOverScalar_Mul_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1854,7 +1851,7 @@ public void test_VectorOverScalar_Mul_Double_Long() throws Exception { } @Test - public void test_VectorOverScalar_Div_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverScalar_Div_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1889,15 +1886,15 @@ public void test_VectorOverScalar_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1916,7 +1913,7 @@ public void test_VectorOverScalar_Div_Long_Long() throws Exception { } @Test - public void test_VectorOverScalar_Div_Long_Double() throws Exception { + public void test_Arithmetic_VectorOverScalar_Div_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -1951,15 +1948,15 @@ public void test_VectorOverScalar_Div_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -1978,7 +1975,7 @@ public void test_VectorOverScalar_Div_Long_Double() throws Exception { } @Test - public void test_VectorOverScalar_Div_Double_Long() throws Exception { + public void test_Arithmetic_VectorOverScalar_Div_Double_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2013,15 +2010,15 @@ public void test_VectorOverScalar_Div_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); @@ -2040,7 +2037,7 @@ public void test_VectorOverScalar_Div_Double_Long() throws Exception { } @Test - public void test_VectorOverVector_Add_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverVector_Add_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2076,15 +2073,15 @@ public void test_VectorOverVector_Add_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2102,7 +2099,7 @@ public void test_VectorOverVector_Add_Long_Long() throws Exception { } @Test - public void test_VectorOverVector_Add_Long_Double() throws Exception { + public void test_Arithmetic_VectorOverVector_Add_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2138,15 +2135,15 @@ public void test_VectorOverVector_Add_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2164,7 +2161,7 @@ public void test_VectorOverVector_Add_Long_Double() throws Exception { } @Test - public void test_VectorOverVector_Add_Double_Double() throws Exception { + public void test_Arithmetic_VectorOverVector_Add_Double_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2201,15 +2198,15 @@ public void test_VectorOverVector_Add_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2227,7 +2224,7 @@ public void test_VectorOverVector_Add_Double_Double() throws Exception { } @Test - public void test_VectorOverVector_Sub_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverVector_Sub_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2263,15 +2260,15 @@ public void test_VectorOverVector_Sub_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2289,7 +2286,7 @@ public void test_VectorOverVector_Sub_Long_Long() throws Exception { } @Test - public void test_VectorOverVector_Sub_Long_Double() throws Exception { + public void test_Arithmetic_VectorOverVector_Sub_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2325,15 +2322,15 @@ public void test_VectorOverVector_Sub_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2351,7 +2348,7 @@ public void test_VectorOverVector_Sub_Long_Double() throws Exception { } @Test - public void test_VectorOverVector_Sub_Double_Long() throws Exception { + public void test_Arithmetic_VectorOverVector_Sub_Double_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2387,15 +2384,15 @@ public void test_VectorOverVector_Sub_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2413,7 +2410,7 @@ public void test_VectorOverVector_Sub_Double_Long() throws Exception { } @Test - public void test_VectorOverVector_Sub_Double_Double() throws Exception { + public void test_Arithmetic_VectorOverVector_Sub_Double_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2449,15 +2446,15 @@ public void test_VectorOverVector_Sub_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "-" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2475,7 +2472,7 @@ public void test_VectorOverVector_Sub_Double_Double() throws Exception { } @Test - public void test_VectorOverVector_Mul_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverVector_Mul_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2511,15 +2508,15 @@ public void test_VectorOverVector_Mul_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2537,7 +2534,7 @@ public void test_VectorOverVector_Mul_Long_Long() throws Exception { } @Test - public void test_VectorOverVector_Mul_Long_Double() throws Exception { + public void test_Arithmetic_VectorOverVector_Mul_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2573,15 +2570,15 @@ public void test_VectorOverVector_Mul_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2599,7 +2596,7 @@ public void test_VectorOverVector_Mul_Long_Double() throws Exception { } @Test - public void test_VectorOverVector_Mul_Double_Long() throws Exception { + public void test_Arithmetic_VectorOverVector_Mul_Double_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2635,15 +2632,15 @@ public void test_VectorOverVector_Mul_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2661,7 +2658,7 @@ public void test_VectorOverVector_Mul_Double_Long() throws Exception { } @Test - public void test_VectorOverVector_Mul_Double_Double() throws Exception { + public void test_Arithmetic_VectorOverVector_Mul_Double_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2698,15 +2695,15 @@ public void test_VectorOverVector_Mul_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "*" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2724,7 +2721,7 @@ public void test_VectorOverVector_Mul_Double_Double() throws Exception { } @Test - public void test_VectorOverVector_Div_Long_Long() throws Exception { + public void test_Arithmetic_VectorOverVector_Div_Long_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2760,15 +2757,15 @@ public void test_VectorOverVector_Div_Long_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2786,7 +2783,7 @@ public void test_VectorOverVector_Div_Long_Long() throws Exception { } @Test - public void test_VectorOverVector_Div_Long_Double() throws Exception { + public void test_Arithmetic_VectorOverVector_Div_Long_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2822,15 +2819,15 @@ public void test_VectorOverVector_Div_Long_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2848,7 +2845,7 @@ public void test_VectorOverVector_Div_Long_Double() throws Exception { } @Test - public void test_VectorOverVector_Div_Double_Long() throws Exception { + public void test_Arithmetic_VectorOverVector_Div_Double_Long() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2884,15 +2881,15 @@ public void test_VectorOverVector_Div_Double_Long() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2910,7 +2907,7 @@ public void test_VectorOverVector_Div_Double_Long() throws Exception { } @Test - public void test_VectorOverVector_Div_Double_Double() throws Exception { + public void test_Arithmetic_VectorOverVector_Div_Double_Double() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -2946,15 +2943,15 @@ public void test_VectorOverVector_Div_Double_Double() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -2972,7 +2969,7 @@ public void test_VectorOverVector_Div_Double_Double() throws Exception { } @Test - public void test_VectorOverVector_NoIntersection() throws Exception { + public void test_Arithmetic_VectorOverVector_NoIntersection() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -3008,15 +3005,15 @@ public void test_VectorOverVector_NoIntersection() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); @@ -3034,7 +3031,7 @@ public void test_VectorOverVector_NoIntersection() throws Exception { * 1 and 2 have no intersection */ @Test - public void test_VectorOverVector_NoIntersection_2() throws Exception { + public void test_Arithmetic_VectorOverVector_NoIntersection_2() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -3084,15 +3081,15 @@ public void test_VectorOverVector_NoIntersection_2() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -3112,7 +3109,7 @@ public void test_VectorOverVector_NoIntersection_2() throws Exception { * 1 and 2 have intersection while 2 and 3 has no intersection */ @Test - public void test_VectorOverVector_NoIntersection_3() throws Exception { + public void test_Arithmetic_VectorOverVector_NoIntersection_3() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -3162,15 +3159,15 @@ public void test_VectorOverVector_NoIntersection_3() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "/" + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + "+" @@ -3186,7 +3183,7 @@ public void test_VectorOverVector_NoIntersection_3() throws Exception { } @Test - public void test_MultipleExpressions() throws Exception { + public void test_Arithmetic_MultipleExpressions() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); @@ -3235,15 +3232,15 @@ public void test_MultipleExpressions() throws Exception { throw new IllegalArgumentException("Invalid metric: " + metric); }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + "/ " + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + "* " @@ -3262,7 +3259,7 @@ public void test_MultipleExpressions() throws Exception { } @Test - public void test_RelativeComparison() throws Exception { + public void test_Arithmetic_RelativeComparison() throws Exception { Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) .thenAnswer((answer) -> { QueryRequest req = answer.getArgument(0, QueryRequest.class); @@ -3284,15 +3281,14 @@ public void test_RelativeComparison() throws Exception { }); - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .dataSourceApi(dataSourceApi) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) > -5%[-1d]"); + // BY is given so that it produces a vector + .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) > -5%[-1d]"); PipelineQueryResult response = evaluator.execute() .get(); Assertions.assertEquals(2, response.getRows()); @@ -3319,35 +3315,33 @@ public void test_RelativeComparison() throws Exception { } @Test - public void test_FilterStep_Double_GT() throws Exception { + public void test_Arithmetic_FilterStep_Double_GT() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) - .thenAnswer((answer) -> { - return new MockReadStep(PipelineQueryResult.builder() - .keyColumns(List.of("_timestamp", "appName")) - .valColumns(List.of("activeThreads")) - .rows(3) - .table(ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 3, 4, 5) - )) - .build(), - false); - }); + .thenAnswer((answer) -> new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 3, 4, 5) + )) + .build(), + false)); // // Case 1, > 2 // { - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 2"); + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 2"); PipelineQueryResult response = evaluator.execute().get(); // all 3 records satisfy the filter condition @@ -3367,15 +3361,15 @@ public void test_FilterStep_Double_GT() throws Exception { // Case 2, > 3, two rows satisfy the filter condition // { - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 3"); + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 3"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(2, response.getRows()); @@ -3391,15 +3385,15 @@ public void test_FilterStep_Double_GT() throws Exception { // Case 3, > 4, 1 rows satisfies the filter condition // { - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 4"); + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 4"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(1, response.getRows()); @@ -3415,15 +3409,15 @@ public void test_FilterStep_Double_GT() throws Exception { // Case 4, > 5, 0 rows satisfies the filter condition // { - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 5"); + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 5"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(0, response.getRows()); @@ -3435,34 +3429,32 @@ public void test_FilterStep_Double_GT() throws Exception { } @Test - public void test_FilterStep_Double_GTE() throws Exception { + public void test_Arithmetic_FilterStep_Double_GTE() throws Exception { Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) - .thenAnswer((answer) -> { - return new MockReadStep(PipelineQueryResult.builder() - .keyColumns(List.of("_timestamp", "appName")) - .valColumns(List.of("activeThreads")) - .rows(3) - .table(ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 3, 4, 5) - )) - .build(), - false); - }); + .thenAnswer((answer) -> new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 3, 4, 5) + )) + .build(), + false)); // // Case 1, >= 2, all 3 rows satisfy the filter condition // { - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 2"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 2"); PipelineQueryResult response = evaluator.execute().get(); // all 3 records satisfy the filter condition @@ -3480,14 +3472,14 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 2, >= 3, all 3 rows satisfy the filter condition // { - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 3"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 3"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(3, response.getRows()); @@ -3504,15 +3496,15 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 3, >= 4, 2 rows satisfies the filter condition // { - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 4"); + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 4"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(2, response.getRows()); @@ -3528,15 +3520,15 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 4, >= 5, some rows satisfy the filter condition // { - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - // BY is given so that it produces a vector - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 5"); + // BY is given so that it produces a vector + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 5"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(1, response.getRows()); @@ -3551,14 +3543,14 @@ public void test_FilterStep_Double_GTE() throws Exception { // Case 5, >= 6, 0 rows satisfies the filter condition // { - IPhysicalPlan evaluator = PhysicalPlanner.builder() - .schemaProvider(this.schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) + .interval(IntervalRequest.builder() .bucketCount(1) .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) .build()) - .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 6"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 6"); PipelineQueryResult response = evaluator.execute().get(); Assertions.assertEquals(0, response.getRows()); diff --git a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java index 7286d6379f..a405802f11 100644 --- a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java +++ b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java @@ -34,7 +34,7 @@ import org.bithon.server.datasource.reader.h2.H2SqlDialect; import org.bithon.server.datasource.reader.jdbc.JdbcDataSourceReader; import org.bithon.server.datasource.store.IDataStoreSpec; -import org.bithon.server.metric.expression.pipeline.PhysicalPlanner; +import org.bithon.server.metric.expression.plan.MetricExpressionPhysicalPlanner; import org.bithon.server.web.service.datasource.api.IntervalRequest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -100,13 +100,13 @@ public IDataSourceReader createReader() { @Test public void testTableScanPlan() { - IPhysicalPlan physicalPlan = PhysicalPlanner.builder() - .schemaProvider(schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) .build()) - .timeSeries("bithon-jvm-metrics.totalCount"); + .timeSeries("bithon-jvm-metrics.totalCount"); Assertions.assertEquals(""" JdbcReadStep @@ -119,13 +119,13 @@ public void testTableScanPlan() { @Test public void testTableScanPlanWithFilter() { - IPhysicalPlan physicalPlan = PhysicalPlanner.builder() - .schemaProvider(schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) .build()) - .timeSeries("bithon-jvm-metrics.totalCount{appName=\"jacky\"}"); + .timeSeries("bithon-jvm-metrics.totalCount{appName=\"jacky\"}"); Assertions.assertEquals(""" JdbcReadStep @@ -141,13 +141,13 @@ public void test_SumAggregate() { String expr = """ sum(bithon-jvm-metrics.totalCount{appName="jacky"}) """; - IPhysicalPlan physicalPlan = PhysicalPlanner.builder() - .schemaProvider(schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) .build()) - .groupBy(expr); + .groupBy(expr); Assertions.assertEquals(""" JdbcReadStep @@ -163,14 +163,14 @@ public void test_SumAggregate_GroupBy() { String expr = """ sum(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) """; - IPhysicalPlan physicalPlan = PhysicalPlanner.builder() - .schemaProvider(schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) .step(60) .build()) - .groupBy(expr); + .groupBy(expr); Assertions.assertEquals(""" JdbcReadStep SELECT "appName", @@ -187,14 +187,14 @@ public void test_SumAggregate_GroupBy_TimeSeries() { String expr = """ sum(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) """; - IPhysicalPlan physicalPlan = PhysicalPlanner.builder() - .schemaProvider(schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) .step(10) .build()) - .timeSeries(expr); + .timeSeries(expr); Assertions.assertEquals(""" JdbcReadStep @@ -214,13 +214,13 @@ public void test_LastAggregate() { last(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) """; - IPhysicalPlan physicalPlan = PhysicalPlanner.builder() - .schemaProvider(schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) .build()) - .groupBy(expr); + .groupBy(expr); Assertions.assertEquals(""" JdbcReadStep @@ -246,13 +246,13 @@ public void test_Arithmetic_TwoExpressions() { sum(bithon-jvm-metrics.count5xx{appName="jacky"}) """; - IPhysicalPlan physicalPlan = PhysicalPlanner.builder() - .schemaProvider(schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) .build()) - .groupBy(expr); + .groupBy(expr); Assertions.assertEquals(""" AddStep, Result Column: value, Retained Columns: [] @@ -278,13 +278,13 @@ public void test_Arithmetic_WithLiteral() { 5 """; - IPhysicalPlan physicalPlan = PhysicalPlanner.builder() - .schemaProvider(schemaProvider) - .interval(IntervalRequest.builder() + IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) .build()) - .groupBy(expr); + .groupBy(expr); Assertions.assertEquals(""" AddStep, Result Column: value, Retained Columns: [] From 26b9aea2409a36ffc88bbae7128b577a1989f745 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Thu, 12 Jun 2025 18:41:11 +0800 Subject: [PATCH 20/22] init support relative comparison --- .../query/plan/logical/LogicalScalar.java | 11 +- .../query/plan/logical/LogicalTableScan.java | 5 +- .../query/plan/physical/ArithmeticStep.java | 8 +- .../query/plan/physical/FilterStep.java | 9 + .../query/plan/physical/IPhysicalPlan.java | 17 + .../reader/jdbc/JdbcDataSourceReader.java | 5 +- .../jdbc/pipeline/JdbcPipelineBuilder.java | 23 +- .../reader/jdbc/pipeline/JdbcReadStep.java | 67 +- .../metric/expression/api/MetricQueryApi.java | 2 +- .../ast/MetricExpressionASTBuilder.java | 7 +- .../plan/MetricExpressionLogicalPlanner.java | 9 +- .../plan/MetricExpressionPhysicalPlanner.java | 98 ++- .../MetricExpressionPhysicalPlannerTest.java | 827 +++++++++--------- .../builder/PhysicalPlannerTest.java | 240 ++--- 14 files changed, 757 insertions(+), 571 deletions(-) diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java index 6795d4d72c..57c1eef3c9 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalScalar.java @@ -16,13 +16,22 @@ package org.bithon.server.datasource.query.plan.logical; +import jakarta.annotation.Nullable; import org.bithon.component.commons.expression.LiteralExpression; +import org.bithon.component.commons.utils.HumanReadableDuration; /** * @author frank.chen021@outlook.com * @date 2025/6/4 23:33 */ -public record LogicalScalar(LiteralExpression literal) implements ILogicalPlan { +public record LogicalScalar( + LiteralExpression literal, + @Nullable HumanReadableDuration offset +) implements ILogicalPlan { + + public LogicalScalar(LiteralExpression literal) { + this(literal, null); + } @Override public T accept(ILogicalPlanVisitor visitor) { diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java index 2b55de97a9..c18e0532b5 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/logical/LogicalTableScan.java @@ -16,7 +16,9 @@ package org.bithon.server.datasource.query.plan.logical; +import jakarta.annotation.Nullable; import org.bithon.component.commons.expression.IExpression; +import org.bithon.component.commons.utils.HumanReadableDuration; import org.bithon.server.datasource.ISchema; import org.bithon.server.datasource.query.ast.Selector; @@ -29,7 +31,8 @@ public record LogicalTableScan( ISchema table, List selectorList, - IExpression filter // = filters like {job="abc"} + IExpression filter, + @Nullable HumanReadableDuration offset ) implements ILogicalPlan { @Override diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ArithmeticStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ArithmeticStep.java index de5a0647b9..e6fd3319a6 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ArithmeticStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/ArithmeticStep.java @@ -63,8 +63,8 @@ public Sub(IPhysicalPlan left, IPhysicalPlan right) { public Sub(IPhysicalPlan left, IPhysicalPlan right, String resultColumn, - String... sourceColumns) { - super(left, right, resultColumn, sourceColumns); + boolean retainAll) { + super(left, right, resultColumn); } @Override @@ -92,8 +92,8 @@ public Div(IPhysicalPlan left, IPhysicalPlan right) { public Div(IPhysicalPlan left, IPhysicalPlan right, String resultColumn, - String... sourceColumns) { - super(left, right, resultColumn, sourceColumns); + boolean retainAll) { + super(left, right, resultColumn); } @Override diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/FilterStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/FilterStep.java index 0224cd0b4e..30ba59d7f4 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/FilterStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/FilterStep.java @@ -90,6 +90,15 @@ public CompletableFuture execute() throws Exception { }); } + @Override + public void serializer(PhysicalPlanSerializer serializer) { + serializer.append("ComparisonStep: "); + serializer.append(this.getClass().getSimpleName()); + serializer.append("\n"); + serializer.append(" ", "lhs: ").append(" ", source.serializeToText()); + serializer.append(" ", "rhs: ").append(" ", expected.toString()).append("\n"); + } + protected abstract boolean filter(long actual, long expected); protected abstract boolean filter(double actual, double expected); diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java index 9d65794c54..6fb5ad311d 100644 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/IPhysicalPlan.java @@ -17,7 +17,9 @@ package org.bithon.server.datasource.query.plan.physical; +import org.bithon.component.commons.utils.HumanReadableDuration; import org.bithon.server.datasource.query.plan.logical.LogicalAggregate; +import org.bithon.server.datasource.query.plan.logical.LogicalFilter; import org.bithon.server.datasource.query.result.PipelineQueryResult; import java.util.concurrent.CompletableFuture; @@ -36,6 +38,21 @@ default IPhysicalPlan pushDownAggregate(LogicalAggregate aggregate) { throw new UnsupportedOperationException("Cannot push down aggregate: " + aggregate); } + default boolean canPushDownFilter() { + return false; + } + + default IPhysicalPlan pushDownFilter(LogicalFilter filter) { + throw new UnsupportedOperationException("Cannot push down filter: " + filter); + } + + /** + * Mutate the plan to apply an offset. + * @param offset can't be null + */ + default IPhysicalPlan offset(HumanReadableDuration offset) { + throw new UnsupportedOperationException("Cannot apply offset: " + offset + " to type " + this.getClass().getSimpleName()); + } boolean isScalar(); diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java index fc1e29498a..a446c410cc 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/JdbcDataSourceReader.java @@ -54,6 +54,7 @@ import org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration; import org.springframework.boot.autoconfigure.jooq.JooqProperties; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -132,7 +133,9 @@ public IPhysicalPlan plan(LogicalTableScan tableScan, Interval interval) { tableScan.table(), sqlDialect, selectStatement, - interval + interval, + tableScan.selectorList().stream().map(Selector::getOutputName).collect(Collectors.toList()), + Collections.emptyList() ); } diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java index 65c1e539ba..1edf8b2446 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java @@ -99,18 +99,24 @@ public IPhysicalPlan build() { } if (windowFunctionSelectors.isEmpty()) { - return new JdbcReadStep(dslContext, schema, dialect, selectStatement, this.interval); + return new JdbcReadStep(dslContext, + schema, + dialect, + selectStatement, + this.interval, + // TODO: Fixme: inputColumns and outputColumns are empty, should we throw an exception? + Collections.emptyList(), + Collections.emptyList()); } WindowFunctionExpression windowFunctionExpression = (WindowFunctionExpression) ((ExpressionNode) windowFunctionSelectors.get(0).getSelectExpression()).getParsedExpression(); LiteralExpression.LongLiteral window = (LiteralExpression.LongLiteral) windowFunctionExpression.getFrame().getStart(); IdentifierExpression orderBy = (IdentifierExpression) windowFunctionExpression.getOrderBy()[0].getName(); - List keys = CollectionUtils.isEmpty(windowFunctionExpression.getPartitionBy()) ? - Collections.emptyList() - : Arrays.stream(windowFunctionExpression.getPartitionBy()) - .map((expr) -> ((IdentifierExpression) expr).getIdentifier()) - .toList(); + List keys = CollectionUtils.isEmpty(windowFunctionExpression.getPartitionBy()) ? Collections.emptyList() + : Arrays.stream(windowFunctionExpression.getPartitionBy()) + .map((expr) -> ((IdentifierExpression) expr).getIdentifier()) + .toList(); SelectStatement subQuery = (SelectStatement) selectStatement.getFrom().getExpression(); @@ -126,7 +132,10 @@ public IPhysicalPlan build() { schema, dialect, subQuery, - interval + interval, + // TODO: Fixme: inputColumns and outputColumns are empty, should we throw an exception? + Collections.emptyList(), + Collections.emptyList() ); return new SlidingWindowAggregationStep(orderBy.getIdentifier(), diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java index b4f176a227..ecf8b6f6d3 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcReadStep.java @@ -20,8 +20,10 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.bithon.component.commons.expression.LogicalExpression; +import org.bithon.component.commons.utils.HumanReadableDuration; import org.bithon.component.commons.utils.StringUtils; import org.bithon.server.datasource.ISchema; +import org.bithon.server.datasource.TimestampSpec; import org.bithon.server.datasource.query.Interval; import org.bithon.server.datasource.query.ast.ExpressionNode; import org.bithon.server.datasource.query.ast.Selector; @@ -38,6 +40,7 @@ import org.jooq.DSLContext; import org.jooq.Record; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -56,12 +59,17 @@ public class JdbcReadStep implements IPhysicalPlan { private final ISqlDialect sqlDialect; private final ISchema schema; private final Interval interval; + private final List keyColumns; + private final List valColumns; + private final HumanReadableDuration offset; public JdbcReadStep(DSLContext dslContext, ISchema schema, ISqlDialect sqlDialect, SelectStatement selectStatement, - Interval interval + Interval interval, + List keyColumns, + List valueColumns ) { this.dslContext = dslContext; this.schema = schema; @@ -69,6 +77,43 @@ public JdbcReadStep(DSLContext dslContext, this.interval = interval; this.sqlDialect = sqlDialect; this.sql = selectStatement.toSQL(sqlDialect); + this.keyColumns = keyColumns; + this.valColumns = valueColumns; + this.offset = null; + } + + public JdbcReadStep(DSLContext dslContext, + ISchema schema, + ISqlDialect sqlDialect, + SelectStatement selectStatement, + Interval interval, + List keyColumns, + List valueColumns, + HumanReadableDuration offset + ) { + this.dslContext = dslContext; + this.schema = schema; + this.selectStatement = selectStatement; + this.interval = interval; + this.sqlDialect = sqlDialect; + this.sql = selectStatement.toSQL(sqlDialect); + this.keyColumns = keyColumns; + this.valColumns = valueColumns; + this.offset = offset; + } + + @Override + public IPhysicalPlan offset(HumanReadableDuration offset) { + return new JdbcReadStep( + this.dslContext, + this.schema, + this.sqlDialect, + this.selectStatement, + this.interval, + this.keyColumns, + this.valColumns, + this.offset + ); } @Override @@ -106,7 +151,20 @@ public IPhysicalPlan pushDownAggregate(LogicalAggregate aggregate) { .filter(LogicalExpression.create(LogicalExpression.AND.OP, selectStatement.getWhere().getExpressions())) .groupBy(aggregate.groupBy()) .build(); - return new JdbcReadStep(dslContext, schema, sqlDialect, select, interval); + + List keys = new ArrayList<>(aggregate.groupBy()); + if (interval.getStep() != null) { + // a time series query + keys.add(TimestampSpec.COLUMN_ALIAS); + } + + return new JdbcReadStep(dslContext, + schema, + sqlDialect, + select, + interval, + keys, + List.of(aggregate.field().getName())); } @Override @@ -114,7 +172,7 @@ public CompletableFuture execute() throws Exception { return CompletableFuture.supplyAsync(() -> { ColumnarTable resultTable = new ColumnarTable(); for (Selector selector : selectStatement.getSelectorList().getSelectors()) { - resultTable.addColumn(Column.create(selector.getOutputName(), selector.getDataType(), 1024)); + resultTable.addColumn(Column.create(selector.getOutputName(), selector.getDataType(), 256)); } List resultColumns = resultTable.getColumns(); @@ -127,7 +185,10 @@ public CompletableFuture execute() throws Exception { } } return PipelineQueryResult.builder() + .rows(resultTable.rowCount()) .table(resultTable) + .keyColumns(this.keyColumns) + .valColumns(this.valColumns) .build(); } }); diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java index c017447c2e..fcb97e799e 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/api/MetricQueryApi.java @@ -91,7 +91,7 @@ public QueryResponse timeSeries(@Validated @RequestBody MetricQueryRequest re .schemaProvider(this.schemaManager) .interval(request.getInterval()) .condition(request.getCondition()) - .build(request.getExpression()); + .timeSeries(request.getExpression()); Duration step = request.getInterval().calculateStep(); TimeSpan start = request.getInterval().getStartISO8601(); diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java index 5e6fd61f01..787077016f 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/ast/MetricExpressionASTBuilder.java @@ -35,6 +35,7 @@ import org.bithon.component.commons.utils.HumanReadableDuration; import org.bithon.component.commons.utils.HumanReadableNumber; import org.bithon.component.commons.utils.HumanReadablePercentage; +import org.bithon.component.commons.utils.Preconditions; import org.bithon.component.commons.utils.StringUtils; import org.bithon.server.commons.antlr4.SyntaxErrorListener; import org.bithon.server.metric.expression.MetricExpressionBaseVisitor; @@ -222,6 +223,9 @@ public IExpression visitMetricFilterExpression(MetricExpressionParser.MetricFilt HumanReadableDuration offset = null; MetricExpressionParser.DurationExpressionContext offsetParseContext = expectedExpression.durationExpression(); if (offsetParseContext != null) { + Preconditions.checkIfTrue(lhs instanceof MetricSelectExpression || lhs instanceof MetricAggregateExpression, + "The offset expression '%s' is only supported for metric select/aggregate expression.", offsetParseContext.getText()); + offset = offsetParseContext.accept(new DurationExpressionBuilder()); if (!offset.isNegative()) { throw new InvalidExpressionException("The value in the offset expression '%s' must be negative.", offsetParseContext.getText()); @@ -405,8 +409,7 @@ private static LiteralExpression toLiteralASTExpression(int tokenType, String return switch (tokenType) { case MetricExpressionParser.DECIMAL_LITERAL -> LiteralExpression.ofDecimal(parseDecimal(text)); case MetricExpressionParser.INTEGER_LITERAL -> LiteralExpression.ofLong(Integer.parseInt(text)); - case MetricExpressionParser.PERCENTAGE_LITERAL -> - LiteralExpression.of(new HumanReadablePercentage(text)); + case MetricExpressionParser.PERCENTAGE_LITERAL -> LiteralExpression.of(new HumanReadablePercentage(text)); case MetricExpressionParser.STRING_LITERAL -> { String input = text; if (!input.isEmpty()) { diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionLogicalPlanner.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionLogicalPlanner.java index 6a52016c30..430b10e26b 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionLogicalPlanner.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionLogicalPlanner.java @@ -60,7 +60,8 @@ public ILogicalPlan visit(MetricSelectExpression expression) { return new LogicalTableScan( schema, List.of(new Selector(expression.getMetric(), expression.getMetric(), expression.getDataType())), - expression.getLabelSelectorExpression() + expression.getLabelSelectorExpression(), + null ); } @@ -76,7 +77,8 @@ public ILogicalPlan visit(MetricAggregateExpression expression) { List.of(new Selector(expression.getMetric().getName(), expression.getMetric().getName(), expression.getDataType())), - expression.getLabelSelectorExpression()), + expression.getLabelSelectorExpression(), + expression.getOffset()), col, CollectionUtils.isEmpty(expression.getGroupBy()) ? new ArrayList<>() : new ArrayList<>(expression.getGroupBy()), new ArrayList<>() @@ -110,6 +112,7 @@ public ILogicalPlan visit(ConditionalExpression expression) { case "!=", "<>" -> PredicateEnum.NE; default -> throw new IllegalArgumentException("Unsupported predicate type: " + expression.getType()); }; + return new LogicalFilter( expression.getLhs().accept(this), predicate, @@ -119,7 +122,7 @@ public ILogicalPlan visit(ConditionalExpression expression) { @Override public ILogicalPlan visit(MetricExpectedExpression expression) { - return new LogicalScalar(expression.getExpected()); + return new LogicalScalar(expression.getExpected(), expression.getOffset()); } @Override diff --git a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlanner.java b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlanner.java index 6e670795e5..d62bf245ed 100644 --- a/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlanner.java +++ b/server/metric-expression/src/main/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlanner.java @@ -23,6 +23,7 @@ import org.bithon.component.commons.expression.IExpression; import org.bithon.component.commons.expression.IdentifierExpression; import org.bithon.component.commons.expression.LiteralExpression; +import org.bithon.component.commons.utils.Preconditions; import org.bithon.component.commons.utils.StringUtils; import org.bithon.server.datasource.ISchemaProvider; import org.bithon.server.datasource.query.IDataSourceReader; @@ -58,7 +59,7 @@ */ public class MetricExpressionPhysicalPlanner { - private IntervalRequest intervalRequest; + private IntervalRequest interval; private String condition; private MetricExpressionPlannerSettings settings = MetricExpressionPlannerSettings.DEFAULT; private ISchemaProvider schemaProvider; @@ -67,8 +68,8 @@ public static MetricExpressionPhysicalPlanner builder() { return new MetricExpressionPhysicalPlanner(); } - public MetricExpressionPhysicalPlanner interval(IntervalRequest intervalRequest) { - this.intervalRequest = intervalRequest; + public MetricExpressionPhysicalPlanner interval(IntervalRequest interval) { + this.interval = interval; return this; } @@ -87,17 +88,9 @@ public MetricExpressionPhysicalPlanner schemaProvider(ISchemaProvider schemaProv return this; } - public IPhysicalPlan build(String expression) { - IExpression expr = MetricExpressionASTBuilder.parse(expression); - - // Apply optimization like constant folding on parsed expression - // The optimization is applied here so that the above parse can be tested separately - expr = MetricExpressionOptimizer.optimize(expr); - - return this.build(expr); - } - public IPhysicalPlan timeSeries(String expression) { + Preconditions.checkNotNull(this.interval, "interval of MetricExpressionPhysicalPlanner can't be null"); + IExpression expr = MetricExpressionASTBuilder.parse(expression); // Apply optimization like constant folding on parsed expression @@ -105,9 +98,9 @@ public IPhysicalPlan timeSeries(String expression) { expr = MetricExpressionOptimizer.optimize(expr); ILogicalPlan logicalPlan = expr.accept(new MetricExpressionLogicalPlanner(schemaProvider)); - return logicalPlan.accept(new PhysicalPlanImpl(Interval.of(this.intervalRequest.getStartISO8601(), - this.intervalRequest.getEndISO8601(), - this.intervalRequest.calculateStep(), + return logicalPlan.accept(new PhysicalPlanImpl(Interval.of(this.interval.getStartISO8601(), + this.interval.getEndISO8601(), + this.interval.calculateStep(), new IdentifierExpression("timestamp")))); } @@ -119,8 +112,8 @@ public IPhysicalPlan groupBy(String expression) { expr = MetricExpressionOptimizer.optimize(expr); ILogicalPlan logicalPlan = expr.accept(new MetricExpressionLogicalPlanner(schemaProvider)); - return logicalPlan.accept(new PhysicalPlanImpl(Interval.of(this.intervalRequest.getStartISO8601(), - this.intervalRequest.getEndISO8601(), + return logicalPlan.accept(new PhysicalPlanImpl(Interval.of(this.interval.getStartISO8601(), + this.interval.getEndISO8601(), null, new IdentifierExpression("timestamp")))); } @@ -174,32 +167,50 @@ public IPhysicalPlan visitScalar(LogicalScalar scalar) { @Override public IPhysicalPlan visitFilter(LogicalFilter filter) { - switch (filter.op()) { - case LT: - return new FilterStep.LT(filter.left().accept(this), + IPhysicalPlan curr = filter.left().accept(this); + + if (filter.right() instanceof LogicalScalar scalar) { + if (scalar.offset() != null) { + // AST level ensures that the left side is a MetricAggregateExpression/MetricSelectExpression + + IPhysicalPlan base = curr.offset(scalar.offset()); + + // convert to expression as: (current - base) / base + curr = new ArithmeticStep.Div( + new ArithmeticStep.Sub( + curr, + base, + // result column name + "diff", + + // Retain all value columns in the result set + true + ), + base, + "delta", + + // Keep the current and base columns in the result set + true + ); + } + } + + return switch (filter.op()) { + case LT -> new FilterStep.LT(curr, (Number) ((LogicalScalar) filter.right()).literal().getValue()); - case LTE: - return new FilterStep.LTE(filter.left().accept(this), - (Number) ((LogicalScalar) filter.right()).literal().getValue()); - case GT: - return new FilterStep.GT(filter.left().accept(this), + case LTE -> new FilterStep.LTE(curr, + (Number) ((LogicalScalar) filter.right()).literal().getValue()); + case GT -> new FilterStep.GT(curr, (Number) ((LogicalScalar) filter.right()).literal().getValue()); - case GTE: - return new FilterStep.GTE(filter.left().accept(this), - (Number) ((LogicalScalar) filter.right()).literal().getValue()); - case NE: - return new FilterStep.NE(filter.left().accept(this), + case GTE -> new FilterStep.GTE(curr, + (Number) ((LogicalScalar) filter.right()).literal().getValue()); + case NE -> new FilterStep.NE(curr, (Number) ((LogicalScalar) filter.right()).literal().getValue()); - default: - throw new UnsupportedOperationException("Unsupported filter operation: " + filter.op()); - } + default -> throw new UnsupportedOperationException("Unsupported filter operation: " + filter.op()); + }; } } - public IPhysicalPlan build(IExpression expression) { - return expression.accept(new Builder()); - } - private class Builder implements IMetricExpressionVisitor { @Override public IPhysicalPlan visit(MetricAggregateExpression expression) { @@ -245,13 +256,14 @@ public IPhysicalPlan visit(MetricAggregateExpression expression) { "diff", // Returns 'curr' as well as the computed result set - metricField.getName() + true ), base, "delta", // Keep the current and base columns in the result set - metricField.getName(), expression.getOffset().toString() + //metricField.getName(), expression.getOffset().toString() + true ); } else { return MetricQueryStep.builder() @@ -296,9 +308,9 @@ public IQueryStep visit(FunctionCallExpression expression) { @Override public IPhysicalPlan visit(LiteralExpression expression) { - return new LiteralQueryStep(expression, Interval.of(intervalRequest.getStartISO8601(), - intervalRequest.getEndISO8601(), - intervalRequest.calculateStep(), + return new LiteralQueryStep(expression, Interval.of(interval.getStartISO8601(), + interval.getEndISO8601(), + interval.calculateStep(), null)); } diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlannerTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlannerTest.java index ea232d09cc..ca70d5a060 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlannerTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlannerTest.java @@ -111,15 +111,29 @@ public IDataSourceReader createReader() { static class MockReadStep implements IPhysicalPlan { private final PipelineQueryResult result; private final boolean isScalar; + private final HumanReadableDuration offset; MockReadStep(PipelineQueryResult result) { this.result = result; this.isScalar = false; + this.offset = null; } MockReadStep(PipelineQueryResult result, boolean isScalar) { this.result = result; this.isScalar = isScalar; + this.offset = null; + } + + MockReadStep(PipelineQueryResult result, boolean isScalar, HumanReadableDuration offset) { + this.result = result; + this.isScalar = isScalar; + this.offset = offset; + } + + @Override + public IPhysicalPlan offset(HumanReadableDuration offset) { + return new MockReadStep(result, isScalar, offset); } @Override @@ -157,10 +171,10 @@ public void test_Arithmetic_ScalarOverLiteral_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -182,10 +196,10 @@ public void test_Arithmetic_ScalarOverLiteral_Add_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 3.3"); PipelineQueryResult response = evaluator.execute().get(); @@ -207,10 +221,10 @@ public void test_Arithmetic_ScalarOverLiteral_Add_Double_Long() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -232,10 +246,10 @@ public void test_Arithmetic_ScalarOverLiteral_Add_Double_Double() throws Excepti IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 2.2"); PipelineQueryResult response = evaluator.execute().get(); @@ -257,10 +271,10 @@ public void test_Arithmetic_ScalarOverSizeLiteral_Add_Long_Long() throws Excepti IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 5Mi"); PipelineQueryResult response = evaluator.execute().get(); @@ -282,10 +296,10 @@ public void test_Arithmetic_ScalarOverPercentageLiteral_Add_Long_Long() throws E IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 90%"); PipelineQueryResult response = evaluator.execute().get(); @@ -307,10 +321,10 @@ public void test_Arithmetic_ScalarOverDurationLiteral_Add_Long_Long() throws Exc IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] + 1h"); PipelineQueryResult response = evaluator.execute().get(); @@ -332,10 +346,10 @@ public void test_Arithmetic_ScalarOverLiteral_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -357,10 +371,10 @@ public void test_Arithmetic_ScalarOverLiteral_Sub_Double_Double() throws Excepti IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] - 2.2"); PipelineQueryResult response = evaluator.execute().get(); @@ -382,10 +396,10 @@ public void test_Arithmetic_ScalarOverLiteral_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -407,10 +421,10 @@ public void test_Arithmetic_ScalarOverLiteral_Mul_Double_Long() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -432,10 +446,10 @@ public void test_Arithmetic_ScalarOverLiteral_Mul_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 5.5"); PipelineQueryResult response = evaluator.execute().get(); @@ -457,10 +471,10 @@ public void test_Arithmetic_ScalarOverLiteral_Mul_Double_Double() throws Excepti IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] * 3"); PipelineQueryResult response = evaluator.execute().get(); @@ -482,10 +496,10 @@ public void test_Arithmetic_ScalarOverLiteral_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -507,10 +521,10 @@ public void test_Arithmetic_ScalarOverLiteral_Div_Double_Long() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20"); PipelineQueryResult response = evaluator.execute().get(); @@ -532,10 +546,10 @@ public void test_Arithmetic_ScalarOverLiteral_Div_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 20.0"); PipelineQueryResult response = evaluator.execute().get(); @@ -557,10 +571,10 @@ public void test_Arithmetic_ScalarOverLiteral_Div_Double_Double() throws Excepti IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] / 3.0"); PipelineQueryResult response = evaluator.execute().get(); @@ -584,14 +598,14 @@ public void test_Arithmetic_VectorOverLiteral_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "+" - + "5"); + + "+" + + "5"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -623,14 +637,14 @@ public void test_Arithmetic_VectorOverLiteral_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "-" - + " 5"); + + "-" + + " 5"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -662,14 +676,14 @@ public void test_Arithmetic_VectorOverLiteral_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "*" - + "5"); + + "*" + + "5"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -701,14 +715,14 @@ public void test_Arithmetic_VectorOverLiteral_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .timeSeries("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "5"); + + "/" + + "5"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -761,14 +775,14 @@ public void test_Arithmetic_ScalarOverScalar_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" - + "+" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "+" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("value"); @@ -813,14 +827,14 @@ public void test_Arithmetic_ScalarOverScalar_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" - + "-" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "-" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("value"); @@ -864,14 +878,14 @@ public void test_Arithmetic_ScalarOverScalar_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" - + "*" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "*" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("value"); @@ -915,14 +929,14 @@ public void test_Arithmetic_ScalarOverScalar_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("value"); @@ -967,14 +981,14 @@ public void test_Arithmetic_ScalarOverVector_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" - + "+" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "+" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("totalThreads"); @@ -1029,14 +1043,14 @@ public void test_Arithmetic_ScalarOverVector_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" - + "-" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "-" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("totalThreads"); @@ -1091,14 +1105,14 @@ public void test_Arithmetic_ScalarOverVector_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" - + "*" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "*" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("totalThreads"); @@ -1153,14 +1167,14 @@ public void test_Arithmetic_ScalarOverVector_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("totalThreads"); @@ -1215,14 +1229,14 @@ public void test_Arithmetic_ScalarOverVector_Div_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("totalThreads"); @@ -1277,14 +1291,14 @@ public void test_Arithmetic_ScalarOverVector_Div_Double_Long() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m]" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("totalThreads"); @@ -1339,14 +1353,14 @@ public void test_Arithmetic_VectorOverScalar_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "+" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "+" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -1400,14 +1414,14 @@ public void test_Arithmetic_VectorOverScalar_Add_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "+" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "+" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -1461,14 +1475,14 @@ public void test_Arithmetic_VectorOverScalar_Add_Double_Long() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "+" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "+" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -1522,14 +1536,14 @@ public void test_Arithmetic_VectorOverScalar_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "-" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "-" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -1583,14 +1597,14 @@ public void test_Arithmetic_VectorOverScalar_Sub_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "-" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "-" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -1644,14 +1658,14 @@ public void test_Arithmetic_VectorOverScalar_Sub_Double_Long() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "-" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "-" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column values = response.getTable().getColumn("activeThreads"); @@ -1705,14 +1719,14 @@ public void test_Arithmetic_VectorOverScalar_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "*" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "*" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("activeThreads"); @@ -1766,14 +1780,14 @@ public void test_Arithmetic_VectorOverScalar_Mul_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "*" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "*" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("activeThreads"); @@ -1827,14 +1841,14 @@ public void test_Arithmetic_VectorOverScalar_Mul_Double_Long() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "*" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "*" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("activeThreads"); @@ -1889,14 +1903,14 @@ public void test_Arithmetic_VectorOverScalar_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("activeThreads"); @@ -1951,14 +1965,14 @@ public void test_Arithmetic_VectorOverScalar_Div_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("activeThreads"); @@ -2013,14 +2027,14 @@ public void test_Arithmetic_VectorOverScalar_Div_Double_Long() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m]"); PipelineQueryResult response = evaluator.execute().get(); Column valCol = response.getTable().getColumn("activeThreads"); @@ -2076,14 +2090,14 @@ public void test_Arithmetic_VectorOverVector_Add_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "+" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "+" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2138,14 +2152,14 @@ public void test_Arithmetic_VectorOverVector_Add_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "+" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "+" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2201,14 +2215,14 @@ public void test_Arithmetic_VectorOverVector_Add_Double_Double() throws Exceptio IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "+" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "+" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2263,14 +2277,14 @@ public void test_Arithmetic_VectorOverVector_Sub_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "-" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "-" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2325,14 +2339,14 @@ public void test_Arithmetic_VectorOverVector_Sub_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "-" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "-" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2387,14 +2401,14 @@ public void test_Arithmetic_VectorOverVector_Sub_Double_Long() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "-" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "-" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2449,14 +2463,14 @@ public void test_Arithmetic_VectorOverVector_Sub_Double_Double() throws Exceptio IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "-" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "-" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2511,14 +2525,14 @@ public void test_Arithmetic_VectorOverVector_Mul_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "*" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "*" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2573,14 +2587,14 @@ public void test_Arithmetic_VectorOverVector_Mul_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "*" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "*" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2635,14 +2649,14 @@ public void test_Arithmetic_VectorOverVector_Mul_Double_Long() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "*" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "*" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2698,14 +2712,14 @@ public void test_Arithmetic_VectorOverVector_Mul_Double_Double() throws Exceptio IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "*" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "*" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2760,14 +2774,14 @@ public void test_Arithmetic_VectorOverVector_Div_Long_Long() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2822,14 +2836,14 @@ public void test_Arithmetic_VectorOverVector_Div_Long_Double() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2884,14 +2898,14 @@ public void test_Arithmetic_VectorOverVector_Div_Double_Long() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -2946,14 +2960,14 @@ public void test_Arithmetic_VectorOverVector_Div_Double_Double() throws Exceptio IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -3008,14 +3022,14 @@ public void test_Arithmetic_VectorOverVector_NoIntersection() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -3084,16 +3098,16 @@ public void test_Arithmetic_VectorOverVector_NoIntersection_2() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "+" - + "avg(jvm-metrics.newThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + + "+" + + "avg(jvm-metrics.newThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -3162,16 +3176,16 @@ public void test_Arithmetic_VectorOverVector_NoIntersection_3() throws Exception IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "/" - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" - + "+" - + "avg(jvm-metrics.newThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); + + "/" + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName)" + + "+" + + "avg(jvm-metrics.newThreads{appName = \"bithon-web-'local\"})[1m] by (appName)"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -3235,17 +3249,17 @@ public void test_Arithmetic_MultipleExpressions() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " - + "/ " - + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " - + "* " - + "avg(jvm-metrics.newThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " - + "+ 5"); + + "/ " + + "avg(jvm-metrics.totalThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + + "* " + + "avg(jvm-metrics.newThreads{appName = \"bithon-web-'local\"})[1m] by (appName) " + + "+ 5"); PipelineQueryResult response = evaluator.execute().get(); // Only the overlapped series will be returned @@ -3260,35 +3274,48 @@ public void test_Arithmetic_MultipleExpressions() throws Exception { @Test public void test_Arithmetic_RelativeComparison() throws Exception { - Mockito.when(dataSourceApi.timeseriesV5(Mockito.any())) + Mockito.when(dataSourceReader.plan(Mockito.any(), Mockito.any())) .thenAnswer((answer) -> { - QueryRequest req = answer.getArgument(0, QueryRequest.class); + LogicalTableScan tableScan = answer.getArgument(0, LogicalTableScan.class); - HumanReadableDuration offset = req.getOffset(); + HumanReadableDuration offset = tableScan.offset(); if (offset == null) { - return ColumnarTable.of( - LongColumn.of("_timestamp", 1, 2, 3), - StringColumn.of("appName", "app1", "app2", "app3"), - DoubleColumn.of("activeThreads", 3, 4, 5) - ); + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("activeThreads")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 1, 2, 3), + StringColumn.of("appName", "app1", "app2", "app3"), + DoubleColumn.of("activeThreads", 3, 4, 5) + )) + .build(), + false); } - return ColumnarTable.of( - LongColumn.of("_timestamp", 2, 3, 4), - StringColumn.of("appName", "app2", "app3", "app4"), - DoubleColumn.of("-1d", 21, 22, 23) - ); - + return new MockReadStep(PipelineQueryResult.builder() + .keyColumns(List.of("_timestamp", "appName")) + .valColumns(List.of("-1d")) + .rows(3) + .table(ColumnarTable.of( + LongColumn.of("_timestamp", 2, 3, 4), + StringColumn.of("appName", "app2", "app3", "app4"), + DoubleColumn.of("-1d", 21, 22, 23) + )) + .build(), + false); }); IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector - .build("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) > -5%[-1d]"); + .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] by (appName) > -5%[-1d]"); + PipelineQueryResult response = evaluator.execute() .get(); Assertions.assertEquals(2, response.getRows()); @@ -3336,10 +3363,10 @@ public void test_Arithmetic_FilterStep_Double_GT() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 2"); PipelineQueryResult response = evaluator.execute().get(); @@ -3364,10 +3391,10 @@ public void test_Arithmetic_FilterStep_Double_GT() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 3"); PipelineQueryResult response = evaluator.execute().get(); @@ -3388,10 +3415,10 @@ public void test_Arithmetic_FilterStep_Double_GT() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 4"); PipelineQueryResult response = evaluator.execute().get(); @@ -3412,10 +3439,10 @@ public void test_Arithmetic_FilterStep_Double_GT() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] > 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -3450,10 +3477,10 @@ public void test_Arithmetic_FilterStep_Double_GTE() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 2"); PipelineQueryResult response = evaluator.execute().get(); @@ -3475,10 +3502,10 @@ public void test_Arithmetic_FilterStep_Double_GTE() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 3"); PipelineQueryResult response = evaluator.execute().get(); @@ -3499,10 +3526,10 @@ public void test_Arithmetic_FilterStep_Double_GTE() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 4"); PipelineQueryResult response = evaluator.execute().get(); @@ -3523,10 +3550,10 @@ public void test_Arithmetic_FilterStep_Double_GTE() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) // BY is given so that it produces a vector .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 5"); PipelineQueryResult response = evaluator.execute().get(); @@ -3546,10 +3573,10 @@ public void test_Arithmetic_FilterStep_Double_GTE() throws Exception { IPhysicalPlan evaluator = MetricExpressionPhysicalPlanner.builder() .schemaProvider(this.schemaProvider) .interval(IntervalRequest.builder() - .bucketCount(1) - .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) - .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) - .build()) + .bucketCount(1) + .startISO8601(TimeSpan.fromISO8601("2023-01-01T00:00:00+08:00")) + .endISO8601(TimeSpan.fromISO8601("2023-01-01T00:01:00+08:00")) + .build()) .groupBy("avg(jvm-metrics.activeThreads{appName = \"bithon-web-'local\"})[1m] >= 6"); PipelineQueryResult response = evaluator.execute().get(); diff --git a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java index a405802f11..97679ed76a 100644 --- a/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java +++ b/server/server-starter/src/test/java/org/bithon/server/storage/jdbc/common/statement/builder/PhysicalPlannerTest.java @@ -103,17 +103,17 @@ public void testTableScanPlan() { IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() .schemaProvider(schemaProvider) .interval(IntervalRequest.builder() - .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) - .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) - .build()) + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) .timeSeries("bithon-jvm-metrics.totalCount"); Assertions.assertEquals(""" - JdbcReadStep - SELECT "totalCount" AS "totalCount" - FROM "bithon-jvm-metrics" AS "bithon-jvm-metrics" - WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') - """, + JdbcReadStep + SELECT "totalCount" AS "totalCount" + FROM "bithon-jvm-metrics" AS "bithon-jvm-metrics" + WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') + """, physicalPlan.serializeToText()); } @@ -122,180 +122,210 @@ public void testTableScanPlanWithFilter() { IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() .schemaProvider(schemaProvider) .interval(IntervalRequest.builder() - .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) - .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) - .build()) + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) .timeSeries("bithon-jvm-metrics.totalCount{appName=\"jacky\"}"); Assertions.assertEquals(""" - JdbcReadStep - SELECT "totalCount" AS "totalCount" - FROM "bithon-jvm-metrics" AS "bithon-jvm-metrics" - WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("appName" = 'jacky') - """, + JdbcReadStep + SELECT "totalCount" AS "totalCount" + FROM "bithon-jvm-metrics" AS "bithon-jvm-metrics" + WHERE ("timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("appName" = 'jacky') + """, physicalPlan.serializeToText()); } @Test public void test_SumAggregate() { String expr = """ - sum(bithon-jvm-metrics.totalCount{appName="jacky"}) - """; + sum(bithon-jvm-metrics.totalCount{appName="jacky"}) + """; IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() .schemaProvider(schemaProvider) .interval(IntervalRequest.builder() - .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) - .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) - .build()) + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) .groupBy(expr); Assertions.assertEquals(""" - JdbcReadStep - SELECT sum("totalCount") AS "totalCount" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - """, + JdbcReadStep + SELECT sum("totalCount") AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + """, physicalPlan.serializeToText()); } @Test public void test_SumAggregate_GroupBy() { String expr = """ - sum(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) - """; + sum(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) + """; IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() .schemaProvider(schemaProvider) .interval(IntervalRequest.builder() - .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) - .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) - .step(60) - .build()) + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .step(60) + .build()) .groupBy(expr); Assertions.assertEquals(""" - JdbcReadStep - SELECT "appName", - sum("totalCount") AS "totalCount" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - GROUP BY "appName" - """, + JdbcReadStep + SELECT "appName", + sum("totalCount") AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + GROUP BY "appName" + """, physicalPlan.serializeToText()); } @Test public void test_SumAggregate_GroupBy_TimeSeries() { String expr = """ - sum(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) - """; + sum(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) + """; IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() .schemaProvider(schemaProvider) .interval(IntervalRequest.builder() - .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) - .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) - .step(10) - .build()) + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .step(10) + .build()) .timeSeries(expr); Assertions.assertEquals(""" - JdbcReadStep - SELECT UNIX_TIMESTAMP("timestamp")/ 10 * 10 AS "_timestamp", - "appName", - sum("totalCount") AS "totalCount" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - GROUP BY "appName", "_timestamp" - """, + JdbcReadStep + SELECT UNIX_TIMESTAMP("timestamp")/ 10 * 10 AS "_timestamp", + "appName", + sum("totalCount") AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + GROUP BY "appName", "_timestamp" + """, physicalPlan.serializeToText()); } @Test public void test_LastAggregate() { String expr = """ - last(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) - """; + last(bithon-jvm-metrics.totalCount{appName="jacky"}) by (appName) + """; IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() .schemaProvider(schemaProvider) .interval(IntervalRequest.builder() - .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) - .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) - .build()) + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) .groupBy(expr); Assertions.assertEquals(""" - JdbcReadStep - SELECT "appName", - "totalCount" - FROM - ( - SELECT "appName", - FIRST_VALUE("totalCount") OVER (PARTITION BY (UNIX_TIMESTAMP("timestamp") / 600) * 600 ORDER BY "timestamp" ASC) AS "totalCount" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - ) - GROUP BY "appName", "totalCount" - """, + JdbcReadStep + SELECT "appName", + "totalCount" + FROM + ( + SELECT "appName", + FIRST_VALUE("totalCount") OVER (PARTITION BY (UNIX_TIMESTAMP("timestamp") / 600) * 600 ORDER BY "timestamp" ASC) AS "totalCount" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + ) + GROUP BY "appName", "totalCount" + """, physicalPlan.serializeToText()); } @Test public void test_Arithmetic_TwoExpressions() { String expr = """ - sum(bithon-jvm-metrics.count4xx{appName="jacky"}) - + - sum(bithon-jvm-metrics.count5xx{appName="jacky"}) - """; + sum(bithon-jvm-metrics.count4xx{appName="jacky"}) + + + sum(bithon-jvm-metrics.count5xx{appName="jacky"}) + """; IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() .schemaProvider(schemaProvider) .interval(IntervalRequest.builder() - .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) - .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) - .build()) + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) .groupBy(expr); Assertions.assertEquals(""" - AddStep, Result Column: value, Retained Columns: [] - lhs:\s - JdbcReadStep - SELECT sum("count4xx") AS "count4xx" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - rhs:\s - JdbcReadStep - SELECT sum("count5xx") AS "count5xx" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - """, + AddStep, Result Column: value, Retained Columns: [] + lhs:\s + JdbcReadStep + SELECT sum("count4xx") AS "count4xx" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + rhs:\s + JdbcReadStep + SELECT sum("count5xx") AS "count5xx" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + """, physicalPlan.serializeToText()); } @Test public void test_Arithmetic_WithLiteral() { String expr = """ - sum(bithon-jvm-metrics.count4xx{appName="jacky"}) - + - 5 - """; + sum(bithon-jvm-metrics.count4xx{appName="jacky"}) + + + 5 + """; IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() .schemaProvider(schemaProvider) .interval(IntervalRequest.builder() - .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) - .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) - .build()) + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) .groupBy(expr); Assertions.assertEquals(""" - AddStep, Result Column: value, Retained Columns: [] - lhs:\s - JdbcReadStep - SELECT sum("count4xx") AS "count4xx" - FROM "bithon_jvm_metrics" - WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') - rhs:\s - 5 - """, + AddStep, Result Column: value, Retained Columns: [] + lhs:\s + JdbcReadStep + SELECT sum("count4xx") AS "count4xx" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + rhs:\s + 5 + """, + physicalPlan.serializeToText()); + } + + @Test + public void test_RelativeComparison() { + String expr = """ + sum(bithon-jvm-metrics.count4xx{appName="jacky"}) + > + 30%[-1d] + """; + + IPhysicalPlan physicalPlan = MetricExpressionPhysicalPlanner.builder() + .schemaProvider(schemaProvider) + .interval(IntervalRequest.builder() + .startISO8601(TimeSpan.fromISO8601("2024-07-26T21:22:00.000+0800")) + .endISO8601(TimeSpan.fromISO8601("2024-07-26T21:32:00.000+0800")) + .build()) + .groupBy(expr); + + Assertions.assertEquals(""" + ComparisonStep: GT + lhs:\s + JdbcReadStep + SELECT sum("count4xx") AS "count4xx" + FROM "bithon_jvm_metrics" + WHERE ("bithon_jvm_metrics"."timestamp" >= '2024-07-26T21:22:00.000+08:00') AND ("bithon_jvm_metrics"."timestamp" < '2024-07-26T21:32:00.000+08:00') AND ("bithon_jvm_metrics"."appName" = 'jacky') + rhs:\s + 30% + + """, physicalPlan.serializeToText()); } } From ae2a899ef4d8a948284da6852c9ac223a5e9f98e Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Thu, 12 Jun 2025 18:44:36 +0800 Subject: [PATCH 21/22] Uplift SlidingWindowAggregate to datasource module --- .../query/plan/physical}/SlidingWindowAggregationStep.java | 3 +-- .../query/plan/physical}/SlidingWindowAggregator.java | 3 +-- .../query/plan/physical}/SlidingWindowAggregatorTest.java | 3 +-- .../datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java | 1 + 4 files changed, 4 insertions(+), 6 deletions(-) rename server/datasource/{reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline => common/src/main/java/org/bithon/server/datasource/query/plan/physical}/SlidingWindowAggregationStep.java (97%) rename server/datasource/{reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline => common/src/main/java/org/bithon/server/datasource/query/plan/physical}/SlidingWindowAggregator.java (97%) rename server/datasource/{reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc => common/src/test/java/org/bithon/server/datasource/query/plan/physical}/SlidingWindowAggregatorTest.java (98%) diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregationStep.java similarity index 97% rename from server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregationStep.java index 6acdeda184..9a3e86044a 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregationStep.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregationStep.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package org.bithon.server.datasource.reader.jdbc.pipeline; +package org.bithon.server.datasource.query.plan.physical; import lombok.extern.slf4j.Slf4j; import org.bithon.server.datasource.TimestampSpec; import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; import org.bithon.server.datasource.query.result.Column; import org.bithon.server.datasource.query.result.ColumnarTable; import org.bithon.server.datasource.query.result.PipelineQueryResult; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregator.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregator.java similarity index 97% rename from server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregator.java rename to server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregator.java index 7c9653e5c4..0c5e5911da 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/SlidingWindowAggregator.java +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregator.java @@ -14,9 +14,8 @@ * limitations under the License. */ -package org.bithon.server.datasource.reader.jdbc.pipeline; +package org.bithon.server.datasource.query.plan.physical; -import org.bithon.server.datasource.query.plan.physical.CompositeKey; import org.bithon.server.datasource.query.result.Column; import org.bithon.server.datasource.query.result.ColumnarTable; diff --git a/server/datasource/reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc/SlidingWindowAggregatorTest.java b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregatorTest.java similarity index 98% rename from server/datasource/reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc/SlidingWindowAggregatorTest.java rename to server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregatorTest.java index fde6fc6535..0456c4c2e2 100644 --- a/server/datasource/reader-jdbc/src/test/java/org/bithon/server/datasource/reader/jdbc/SlidingWindowAggregatorTest.java +++ b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregatorTest.java @@ -14,12 +14,11 @@ * limitations under the License. */ -package org.bithon.server.datasource.reader.jdbc; +package org.bithon.server.datasource.query.plan.physical; import org.bithon.server.datasource.query.result.Column; import org.bithon.server.datasource.query.result.ColumnarTable; -import org.bithon.server.datasource.reader.jdbc.pipeline.SlidingWindowAggregator; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java index 1edf8b2446..4e94c9c777 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java @@ -26,6 +26,7 @@ import org.bithon.server.datasource.query.ast.ExpressionNode; import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; +import org.bithon.server.datasource.query.plan.physical.SlidingWindowAggregationStep; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.statement.ast.OrderByClause; import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; From b3f161f5c3e770ee89913cdd1e208038e344769e Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Thu, 12 Jun 2025 18:47:06 +0800 Subject: [PATCH 22/22] Merge SlidingWindowAggregateStep and SlidingWindowAggregator --- .../physical/SlidingWindowAggregateStep.java | 199 ++++++++++++++++++ .../SlidingWindowAggregationStep.java | 120 ----------- .../physical/SlidingWindowAggregator.java | 110 ---------- ...va => SlidingWindowAggregateStepTest.java} | 24 +-- .../jdbc/pipeline/JdbcPipelineBuilder.java | 16 +- .../MetricExpressionPhysicalPlannerTest.java | 1 - 6 files changed, 219 insertions(+), 251 deletions(-) create mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregateStep.java delete mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregationStep.java delete mode 100644 server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregator.java rename server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/{SlidingWindowAggregatorTest.java => SlidingWindowAggregateStepTest.java} (86%) diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregateStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregateStep.java new file mode 100644 index 0000000000..15fc7788b1 --- /dev/null +++ b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregateStep.java @@ -0,0 +1,199 @@ +/* + * Copyright 2020 bithon.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bithon.server.datasource.query.plan.physical; + + +import lombok.extern.slf4j.Slf4j; +import org.bithon.server.datasource.TimestampSpec; +import org.bithon.server.datasource.query.Interval; +import org.bithon.server.datasource.query.result.Column; +import org.bithon.server.datasource.query.result.ColumnarTable; +import org.bithon.server.datasource.query.result.PipelineQueryResult; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * @author frank.chen021@outlook.com + * @date 5/5/25 6:20 pm + */ +@Slf4j +public class SlidingWindowAggregateStep implements IPhysicalPlan { + private final String tsField; + private final List keyFields; + private final List valueFields; + private final Duration window; + private final List resultFields; + private final Interval interval; + private final IPhysicalPlan source; + + public SlidingWindowAggregateStep(String tsField, + List keyFields, + List valueFields, + List resultFields, + Duration window, + Interval interval, + IPhysicalPlan source) { + this.tsField = tsField; + this.keyFields = keyFields; + this.valueFields = valueFields; + this.resultFields = resultFields; + this.window = window; + this.interval = interval; + this.source = source; + } + + @Override + public String toString() { + return "SlidingWindowAggregationStep{" + + "\n\ttsField='" + tsField + '\'' + + "\n\tkeyFields=" + keyFields + + "\n\tvalueFields=" + valueFields + + "\n\twindow=" + window + + "\n\tresultFields=" + resultFields + + "\n\tinterval=" + interval + + '}'; + } + + @Override + public boolean isScalar() { + return false; + } + + @Override + public CompletableFuture execute() throws Exception { + return source.execute() + .thenApply((result) -> { + ColumnarTable resultTable = result.getTable(); + + log.info("Applying sliding window aggregation: \n {}", this.toString()); + + // window aggregation + resultTable = doAggregate(resultTable, + this.tsField, + this.keyFields, + this.window, + this.valueFields); + + // + // The query interval is extended to include the first record of the sliding window + // which is not included in the result set. So we need to filter out some records record + long startingPoint = this.interval.getStartTime() + .floor(interval.getStep()) + .getSeconds(); + + Column tsColumn = resultTable.getColumn(TimestampSpec.COLUMN_ALIAS); + + int selectionIndex = 0; + int[] selections = new int[resultTable.rowCount()]; + for (int i = 0, rowCount = resultTable.rowCount(); i < rowCount; i++) { + long ts = tsColumn.getLong(i); + if (ts >= startingPoint) { + selections[selectionIndex++] = i; + } + } + + resultTable = resultTable.view(selections, selectionIndex); + return PipelineQueryResult.builder() + .table(resultTable) + .rows(resultTable.rowCount()) + .keyColumns(result.getKeyColumns()) + .valColumns(result.getValColumns()) + .build(); + }); + } + + /** + * Computes a moving window sum over a list of sorted time series records. + * + *

Each input row is expected to contain: + *

    + *
  • A numeric "timestamp" field (in seconds)
  • + *
  • A numeric value field indicated by valueField parameter
  • + *
  • One or more grouping key fields (e.g., "clusterName")
  • + *
+ * + *

The input list must be pre-sorted by the grouping keys (in order) and then by timestamp. + * For each row, this method computes the sum of all "value" fields from rows with the same group key + * whose timestamps fall within the range {@code [current.timestamp - windowSeconds, current.timestamp]}. + * + *

The result is a new list of maps, each containing all original key-value pairs from the input row, + * plus an additional entry valueField, holding the computed sum. + * + * @param tsField The name of field where timestamp is stored (e.g., "_timestamp") + * @param keys List of key field names used for grouping (e.g., ["clusterName"]) + * @param window Size of the time window in seconds + */ + public static ColumnarTable doAggregate(ColumnarTable table, + String tsField, + List keys, + Duration window, + List inputFields) { + Column tsColumn = table.getColumn(tsField); + + List keyColumns = table.getColumns(keys); + + int rowCount = table.rowCount(); + List inputColumns = new ArrayList<>(inputFields.size()); + List outputColumns = new ArrayList<>(inputFields.size()); + for (String valueField : inputFields) { + Column valColumn = table.getColumn(valueField); + inputColumns.add(valColumn); + outputColumns.add(Column.create(valueField, valColumn.getDataType(), rowCount)); + } + + CompositeKey prevKey = rowCount > 0 ? CompositeKey.from(keyColumns, 0) : null; + + // The starting window index of a group + int windowStart = 0; + double[] sums = new double[inputFields.size()]; + + for (int i = 0; i < rowCount; i++) { + long ts = tsColumn.getLong(i); + CompositeKey currKey = CompositeKey.from(keyColumns, i); + + if (!currKey.equals(prevKey)) { + // Reset window if group changes + windowStart = i; + Arrays.fill(sums, 0.0); + prevKey = currKey; + } + + // Slide window start to maintain [ts - window, ts] + while (windowStart < i && tsColumn.getLong(windowStart) <= ts - window.getSeconds()) { + for (int j = 0; j < inputColumns.size(); j++) { + sums[j] -= inputColumns.get(j).getDouble(windowStart); + } + windowStart++; + } + + for (int j = 0; j < inputColumns.size(); j++) { + sums[j] += inputColumns.get(j).getDouble(i); + outputColumns.get(j).addObject(sums[j]); + } + } + + ColumnarTable resultTable = new ColumnarTable(); + resultTable.addColumns(keyColumns); + resultTable.addColumn(tsColumn); + resultTable.addColumns(outputColumns); + return resultTable; + } +} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregationStep.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregationStep.java deleted file mode 100644 index 9a3e86044a..0000000000 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregationStep.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2020 bithon.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bithon.server.datasource.query.plan.physical; - - -import lombok.extern.slf4j.Slf4j; -import org.bithon.server.datasource.TimestampSpec; -import org.bithon.server.datasource.query.Interval; -import org.bithon.server.datasource.query.result.Column; -import org.bithon.server.datasource.query.result.ColumnarTable; -import org.bithon.server.datasource.query.result.PipelineQueryResult; - -import java.time.Duration; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * @author frank.chen021@outlook.com - * @date 5/5/25 6:20 pm - */ -@Slf4j -public class SlidingWindowAggregationStep implements IPhysicalPlan { - private final String tsField; - private final List keyFields; - private final List valueFields; - private final Duration window; - private final List resultFields; - private final Interval interval; - private final IPhysicalPlan source; - - public SlidingWindowAggregationStep(String tsField, - List keyFields, - List valueFields, - List resultFields, - Duration window, - Interval interval, - IPhysicalPlan source) { - this.tsField = tsField; - this.keyFields = keyFields; - this.valueFields = valueFields; - this.resultFields = resultFields; - this.window = window; - this.interval = interval; - this.source = source; - } - - @Override - public String toString() { - return "SlidingWindowAggregationStep{" + - "\n\ttsField='" + tsField + '\'' + - "\n\tkeyFields=" + keyFields + - "\n\tvalueFields=" + valueFields + - "\n\twindow=" + window + - "\n\tresultFields=" + resultFields + - "\n\tinterval=" + interval + - '}'; - } - - @Override - public boolean isScalar() { - return false; - } - - @Override - public CompletableFuture execute() throws Exception { - return source.execute() - .thenApply((result) -> { - ColumnarTable resultTable = result.getTable(); - - log.info("Applying sliding window aggregation: \n {}", this.toString()); - - // window aggregation - resultTable = SlidingWindowAggregator.aggregate(resultTable, - this.tsField, - this.keyFields, - this.window, - this.valueFields); - - // - // The query interval is extended to include the first record of the sliding window - // which is not included in the result set. So we need to filter out some records record - long startingPoint = this.interval.getStartTime() - .floor(interval.getStep()) - .getSeconds(); - - Column tsColumn = resultTable.getColumn(TimestampSpec.COLUMN_ALIAS); - - int selectionIndex = 0; - int[] selections = new int[resultTable.rowCount()]; - for (int i = 0, rowCount = resultTable.rowCount(); i < rowCount; i++) { - long ts = tsColumn.getLong(i); - if (ts >= startingPoint) { - selections[selectionIndex++] = i; - } - } - - resultTable = resultTable.view(selections, selectionIndex); - return PipelineQueryResult.builder() - .table(resultTable) - .rows(resultTable.rowCount()) - .keyColumns(result.getKeyColumns()) - .valColumns(result.getValColumns()) - .build(); - }); - } -} diff --git a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregator.java b/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregator.java deleted file mode 100644 index 0c5e5911da..0000000000 --- a/server/datasource/common/src/main/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregator.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2020 bithon.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bithon.server.datasource.query.plan.physical; - -import org.bithon.server.datasource.query.result.Column; -import org.bithon.server.datasource.query.result.ColumnarTable; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - - -/** - * @author frank.chen021@outlook.com - * @date 4/5/25 1:00 pm - */ -public class SlidingWindowAggregator { - - /** - * Computes a moving window sum over a list of sorted time series records. - * - *

Each input row is expected to contain: - *

    - *
  • A numeric "timestamp" field (in seconds)
  • - *
  • A numeric value field indicated by valueField parameter
  • - *
  • One or more grouping key fields (e.g., "clusterName")
  • - *
- * - *

The input list must be pre-sorted by the grouping keys (in order) and then by timestamp. - * For each row, this method computes the sum of all "value" fields from rows with the same group key - * whose timestamps fall within the range {@code [current.timestamp - windowSeconds, current.timestamp]}. - * - *

The result is a new list of maps, each containing all original key-value pairs from the input row, - * plus an additional entry valueField, holding the computed sum. - * - * @param tsField The name of field where timestamp is stored (e.g., "_timestamp") - * @param keys List of key field names used for grouping (e.g., ["clusterName"]) - * @param window Size of the time window in seconds - */ - public static ColumnarTable aggregate(ColumnarTable table, - String tsField, - List keys, - Duration window, - List inputFields) { - Column tsColumn = table.getColumn(tsField); - - List keyColumns = table.getColumns(keys); - - int rowCount = table.rowCount(); - List inputColumns = new ArrayList<>(inputFields.size()); - List outputColumns = new ArrayList<>(inputFields.size()); - for (String valueField : inputFields) { - Column valColumn = table.getColumn(valueField); - inputColumns.add(valColumn); - outputColumns.add(Column.create(valueField, valColumn.getDataType(), rowCount)); - } - - CompositeKey prevKey = rowCount > 0 ? CompositeKey.from(keyColumns, 0) : null; - - // The starting window index of a group - int windowStart = 0; - double[] sums = new double[inputFields.size()]; - - for (int i = 0; i < rowCount; i++) { - long ts = tsColumn.getLong(i); - CompositeKey currKey = CompositeKey.from(keyColumns, i); - - if (!currKey.equals(prevKey)) { - // Reset window if group changes - windowStart = i; - Arrays.fill(sums, 0.0); - prevKey = currKey; - } - - // Slide window start to maintain [ts - window, ts] - while (windowStart < i && tsColumn.getLong(windowStart) <= ts - window.getSeconds()) { - for (int j = 0; j < inputColumns.size(); j++) { - sums[j] -= inputColumns.get(j).getDouble(windowStart); - } - windowStart++; - } - - for (int j = 0; j < inputColumns.size(); j++) { - sums[j] += inputColumns.get(j).getDouble(i); - outputColumns.get(j).addObject(sums[j]); - } - } - - ColumnarTable resultTable = new ColumnarTable(); - resultTable.addColumns(keyColumns); - resultTable.addColumn(tsColumn); - resultTable.addColumns(outputColumns); - return resultTable; - } -} diff --git a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregatorTest.java b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregateStepTest.java similarity index 86% rename from server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregatorTest.java rename to server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregateStepTest.java index 0456c4c2e2..36c204db57 100644 --- a/server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregatorTest.java +++ b/server/datasource/common/src/test/java/org/bithon/server/datasource/query/plan/physical/SlidingWindowAggregateStepTest.java @@ -29,7 +29,7 @@ * @author frank.chen021@outlook.com * @date 5/5/25 3:03 pm */ -public class SlidingWindowAggregatorTest { +public class SlidingWindowAggregateStepTest { @Test void testAggregate_NoGroup_OneRow() { @@ -43,7 +43,7 @@ void testAggregate_NoGroup_OneRow() { // Add rows using addRow table.addRow(1L, 10.0); - table = SlidingWindowAggregator.aggregate(table, "_timestamp", List.of(), Duration.ofSeconds(2), List.of("value")); + table = SlidingWindowAggregateStep.doAggregate(table, "_timestamp", List.of(), Duration.ofSeconds(2), List.of("value")); Column agg = table.getColumn("value"); Assertions.assertEquals(1, table.rowCount()); @@ -64,7 +64,7 @@ void testAggregate_NoGroup() { table.addRow(2L, 20.0); table.addRow(3L, 30.0); - table = SlidingWindowAggregator.aggregate(table, "_timestamp", List.of(), Duration.ofSeconds(2), List.of("value")); + table = SlidingWindowAggregateStep.doAggregate(table, "_timestamp", List.of(), Duration.ofSeconds(2), List.of("value")); Column agg = table.getColumn("value"); Assertions.assertEquals(3, table.rowCount()); @@ -89,7 +89,7 @@ void testAggregate_SingleGroup() { table.addRow(2L, 20.0, "A"); table.addRow(3L, 30.0, "A"); - table = SlidingWindowAggregator.aggregate(table, "_timestamp", List.of("group"), Duration.ofSeconds(2), List.of("value")); + table = SlidingWindowAggregateStep.doAggregate(table, "_timestamp", List.of("group"), Duration.ofSeconds(2), List.of("value")); Column agg = table.getColumn("value"); Assertions.assertEquals(3, table.rowCount()); @@ -117,7 +117,7 @@ void testAggregate_MultipleGroups() { table.addRow(2L, 200.0, "B"); table.addRow(3L, 300.0, "B"); - table = SlidingWindowAggregator.aggregate(table, "_timestamp", List.of("group"), Duration.ofSeconds(2), List.of("value")); + table = SlidingWindowAggregateStep.doAggregate(table, "_timestamp", List.of("group"), Duration.ofSeconds(2), List.of("value")); Column agg = table.getColumn("value"); Assertions.assertEquals(6, table.rowCount()); @@ -145,7 +145,7 @@ void testAggregate_WindowEviction() { table.addRow(5L, 2.0, "A"); table.addRow(10L, 3.0, "A"); - table = SlidingWindowAggregator.aggregate(table, "_timestamp", List.of("group"), Duration.ofSeconds(3), List.of("value")); + table = SlidingWindowAggregateStep.doAggregate(table, "_timestamp", List.of("group"), Duration.ofSeconds(3), List.of("value")); Column agg = table.getColumn("value"); Assertions.assertEquals(3, table.rowCount()); @@ -171,7 +171,7 @@ void testAggregate_WindowEviction_2() { table.addRow(4L, 3.0, "A"); table.addRow(6L, 4.0, "A"); - table = SlidingWindowAggregator.aggregate(table, "_timestamp", List.of("group"), Duration.ofSeconds(3), List.of("value")); + table = SlidingWindowAggregateStep.doAggregate(table, "_timestamp", List.of("group"), Duration.ofSeconds(3), List.of("value")); Column agg = table.getColumn("value"); Assertions.assertEquals(4, table.rowCount()); @@ -206,11 +206,11 @@ void testAggregate_MultipleInputFields() { table.addRow(2L, 15.0, 150.0, "B"); - table = SlidingWindowAggregator.aggregate(table, - "_timestamp", - List.of("group"), - Duration.ofSeconds(2), - List.of("value1", "value2")); + table = SlidingWindowAggregateStep.doAggregate(table, + "_timestamp", + List.of("group"), + Duration.ofSeconds(2), + List.of("value1", "value2")); Column agg1 = table.getColumn("value1"); Column agg2 = table.getColumn("value2"); diff --git a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java index 4e94c9c777..a8c00f46e7 100644 --- a/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java +++ b/server/datasource/reader-jdbc/src/main/java/org/bithon/server/datasource/reader/jdbc/pipeline/JdbcPipelineBuilder.java @@ -26,7 +26,7 @@ import org.bithon.server.datasource.query.ast.ExpressionNode; import org.bithon.server.datasource.query.ast.Selector; import org.bithon.server.datasource.query.plan.physical.IPhysicalPlan; -import org.bithon.server.datasource.query.plan.physical.SlidingWindowAggregationStep; +import org.bithon.server.datasource.query.plan.physical.SlidingWindowAggregateStep; import org.bithon.server.datasource.reader.jdbc.dialect.ISqlDialect; import org.bithon.server.datasource.reader.jdbc.statement.ast.OrderByClause; import org.bithon.server.datasource.reader.jdbc.statement.ast.SelectStatement; @@ -139,12 +139,12 @@ public IPhysicalPlan build() { Collections.emptyList() ); - return new SlidingWindowAggregationStep(orderBy.getIdentifier(), - keys, - inputColumns, - outputColumns, - Duration.ofSeconds(window.getValue()), - this.interval, - readStep); + return new SlidingWindowAggregateStep(orderBy.getIdentifier(), + keys, + inputColumns, + outputColumns, + Duration.ofSeconds(window.getValue()), + this.interval, + readStep); } } diff --git a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlannerTest.java b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlannerTest.java index ca70d5a060..3a2e84afc0 100644 --- a/server/metric-expression/src/test/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlannerTest.java +++ b/server/metric-expression/src/test/java/org/bithon/server/metric/expression/plan/MetricExpressionPhysicalPlannerTest.java @@ -39,7 +39,6 @@ import org.bithon.server.datasource.store.IDataStoreSpec; import org.bithon.server.web.service.datasource.api.IDataSourceApi; import org.bithon.server.web.service.datasource.api.IntervalRequest; -import org.bithon.server.web.service.datasource.api.QueryRequest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test;