This is an automated email from the ASF dual-hosted git repository.
siddteotia pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new e6d3dcdbda [multistage] Fix leaf stage return (#9892)
e6d3dcdbda is described below
commit e6d3dcdbdaf9e08f0ffd939e55361bb946b03f93
Author: Rong Rong <[email protected]>
AuthorDate: Mon Dec 5 15:51:07 2022 -0800
[multistage] Fix leaf stage return (#9892)
* initial commit to move schema logic into LeafStageTransfer
only work with selection result block. others to be add later
* refactor
* adding distinct handling
* also support agg and group by
* add tests
* adding more tests
Co-authored-by: Rong Rong <[email protected]>
---
.../apache/pinot/query/runtime/QueryRunner.java | 46 +---
.../LeafStageTransferableBlockOperator.java | 234 +++++++++++++++++++--
.../LeafStageTransferableBlockOperatorTest.java | 78 +++++--
.../src/test/resources/queries/Aggregates.json | 93 +++++---
.../src/test/resources/queries/Distincts.json | 47 +++++
.../test/resources/queries/LexicalStructure.json | 21 +-
.../test/resources/queries/SelectExpressions.json | 15 +-
7 files changed, 414 insertions(+), 120 deletions(-)
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java
index 7b69aa40ce..5cf1cfa999 100644
---
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java
@@ -20,10 +20,8 @@ package org.apache.pinot.query.runtime;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.concurrent.ExecutorService;
import org.apache.helix.HelixManager;
import org.apache.helix.store.zk.ZkHelixPropertyStore;
@@ -31,13 +29,10 @@ import org.apache.helix.zookeeper.datamodel.ZNRecord;
import org.apache.pinot.common.exception.QueryException;
import org.apache.pinot.common.metadata.ZKMetadataProvider;
import org.apache.pinot.common.metrics.ServerMetrics;
-import org.apache.pinot.common.utils.DataSchema;
import org.apache.pinot.core.data.manager.InstanceDataManager;
import org.apache.pinot.core.operator.blocks.InstanceResponseBlock;
-import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock;
import org.apache.pinot.core.query.executor.ServerQueryExecutorV1Impl;
import org.apache.pinot.core.query.request.ServerQueryRequest;
-import org.apache.pinot.core.query.selection.SelectionOperatorUtils;
import org.apache.pinot.core.transport.ServerInstance;
import org.apache.pinot.query.mailbox.MailboxService;
import org.apache.pinot.query.mailbox.MultiplexingMailboxService;
@@ -189,47 +184,10 @@ public class QueryRunner {
private InstanceResponseBlock processServerQuery(ServerQueryRequest
serverQueryRequest,
ExecutorService executorService) {
try {
- InstanceResponseBlock result =
_serverExecutor.execute(serverQueryRequest, executorService);
-
- if (result.getRows() != null &&
serverQueryRequest.getQueryContext().getOrderByExpressions() != null) {
- // we only re-arrange columns to match the projection in the case of
order by - this is to ensure
- // that V1 results match what the expected projection schema in the
calcite logical operator; if
- // we realize that there are other situations where we need to
post-process v1 results to adhere to
- // the expected results we should factor this out and also apply the
canonicalization of the data
- // types during this post-process step (also see
LeafStageTransferableBlockOperator#canonicalizeRow)
- DataSchema dataSchema = result.getDataSchema();
- List<String> selectionColumns =
-
SelectionOperatorUtils.getSelectionColumns(serverQueryRequest.getQueryContext(),
dataSchema);
-
- int[] columnIndices =
SelectionOperatorUtils.getColumnIndices(selectionColumns, dataSchema);
- int numColumns = columnIndices.length;
-
- DataSchema resultDataSchema =
SelectionOperatorUtils.getSchemaForProjection(dataSchema, columnIndices);
-
- // Extract the result rows
- LinkedList<Object[]> rowsInSelectionResults = new LinkedList<>();
- for (Object[] row : result.getRows()) {
- assert row != null;
- Object[] extractedRow = new Object[numColumns];
- for (int i = 0; i < numColumns; i++) {
- Object value = row[columnIndices[i]];
- if (value != null) {
- extractedRow[i] = value;
- }
- }
-
- rowsInSelectionResults.addFirst(extractedRow);
- }
-
- return new InstanceResponseBlock(
- new SelectionResultsBlock(resultDataSchema,
rowsInSelectionResults),
- serverQueryRequest.getQueryContext());
- } else {
- return result;
- }
+ return _serverExecutor.execute(serverQueryRequest, executorService);
} catch (Exception e) {
InstanceResponseBlock errorResponse = new InstanceResponseBlock();
-
errorResponse.getExceptions().put(QueryException.QUERY_EXECUTION_ERROR_CODE,
Objects.toString(e.getMessage()));
+
errorResponse.getExceptions().put(QueryException.QUERY_EXECUTION_ERROR_CODE,
e.getMessage());
return errorResponse;
}
}
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java
index 551955c388..8def59dd32 100644
---
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperator.java
@@ -18,11 +18,14 @@
*/
package org.apache.pinot.query.runtime.operator;
+import com.clearspring.analytics.util.Preconditions;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.pinot.common.datablock.DataBlock;
import org.apache.pinot.common.datablock.DataBlockUtils;
@@ -32,7 +35,13 @@ import org.apache.pinot.common.utils.DataSchema;
import org.apache.pinot.core.common.Operator;
import org.apache.pinot.core.operator.BaseOperator;
import org.apache.pinot.core.operator.blocks.InstanceResponseBlock;
+import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock;
import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock;
+import org.apache.pinot.core.operator.blocks.results.DistinctResultsBlock;
+import org.apache.pinot.core.operator.blocks.results.GroupByResultsBlock;
+import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock;
+import
org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction;
+import org.apache.pinot.core.query.selection.SelectionOperatorUtils;
import org.apache.pinot.query.runtime.blocks.TransferableBlock;
@@ -54,10 +63,12 @@ public class LeafStageTransferableBlockOperator extends
BaseOperator<Transferabl
private final InstanceResponseBlock _errorBlock;
private final List<InstanceResponseBlock> _baseResultBlock;
+ private final DataSchema _desiredDataSchema;
private int _currentIndex;
public LeafStageTransferableBlockOperator(List<InstanceResponseBlock>
baseResultBlock, DataSchema dataSchema) {
_baseResultBlock = baseResultBlock;
+ _desiredDataSchema = dataSchema;
_errorBlock = baseResultBlock.stream().filter(e ->
!e.getExceptions().isEmpty()).findFirst().orElse(null);
_currentIndex = 0;
}
@@ -84,13 +95,10 @@ public class LeafStageTransferableBlockOperator extends
BaseOperator<Transferabl
} else {
if (_currentIndex < _baseResultBlock.size()) {
InstanceResponseBlock responseBlock =
_baseResultBlock.get(_currentIndex++);
- BaseResultsBlock resultsBlock = responseBlock.getResultsBlock();
- if (resultsBlock != null) {
- List<Object[]> rows =
- toList(resultsBlock.getRows(responseBlock.getQueryContext()),
responseBlock.getDataSchema());
- return new TransferableBlock(rows, responseBlock.getDataSchema(),
DataBlock.Type.ROW);
+ if (responseBlock.getResultsBlock() != null) {
+ return composeTransferableBlock(responseBlock, _desiredDataSchema);
} else {
- return new TransferableBlock(Collections.emptyList(),
responseBlock.getDataSchema(), DataBlock.Type.ROW);
+ return new TransferableBlock(Collections.emptyList(),
_desiredDataSchema, DataBlock.Type.ROW);
}
} else {
_currentIndex = -1;
@@ -99,6 +107,181 @@ public class LeafStageTransferableBlockOperator extends
BaseOperator<Transferabl
}
}
+ /**
+ * this is data transfer block compose method is here to ensure that V1
results match what the expected projection
+ * schema in the calcite logical operator.
+ * <p> it applies different clean up mechanism based on different type of
{@link BaseResultsBlock} and the
+ * {@link org.apache.pinot.core.query.request.context.QueryContext}.</p>
+ * <p> this also applies to the canonicalization of the data types during
post post-process step.</p>
+ *
+ * @param responseBlock result block from leaf stage
+ * @param desiredDataSchema the desired schema for send operator
+ * @return the converted {@link TransferableBlock} that conform with the
desiredDataSchema
+ */
+ private static TransferableBlock
composeTransferableBlock(InstanceResponseBlock responseBlock,
+ DataSchema desiredDataSchema) {
+ BaseResultsBlock resultsBlock = responseBlock.getResultsBlock();
+ if (resultsBlock instanceof SelectionResultsBlock) {
+ return composeSelectTransferableBlock(responseBlock, desiredDataSchema);
+ } else if (resultsBlock instanceof AggregationResultsBlock) {
+ return composeAggregationTransferableBlock(responseBlock,
desiredDataSchema);
+ } else if (resultsBlock instanceof GroupByResultsBlock) {
+ return composeGroupByTransferableBlock(responseBlock, desiredDataSchema);
+ } else if (resultsBlock instanceof DistinctResultsBlock) {
+ return composeDistinctTransferableBlock(responseBlock,
desiredDataSchema);
+ } else {
+ throw new IllegalArgumentException("Unsupported result block type: " +
resultsBlock);
+ }
+ }
+
+ /**
+ * we only need to rearrange columns when distinct is not conforming with
selection columns, specifically:
+ * <ul>
+ * <li> when distinct is not returning final result:
+ * it should never happen as non-final result contains Object opaque
columns v2 engine can't process.</li>
+ * <li> when distinct columns are not all being selected:
+ * it should never happen as leaf stage MUST return the entire
list.</li>
+ * </ul>
+ *
+ * @see
LeafStageTransferableBlockOperator#composeTransferableBlock(InstanceResponseBlock,
DataSchema).
+ */
+ @SuppressWarnings("ConstantConditions")
+ private static TransferableBlock
composeDistinctTransferableBlock(InstanceResponseBlock responseBlock,
+ DataSchema desiredDataSchema) {
+ List<String> selectionColumns = Arrays.asList(
+ ((DistinctAggregationFunction)
responseBlock.getQueryContext().getAggregationFunctions()[0]).getColumns());
+ int[] columnIndices =
SelectionOperatorUtils.getColumnIndices(selectionColumns, desiredDataSchema);
+ Preconditions.checkState(inOrder(columnIndices), "Incompatible distinct
table schema for leaf stage."
+ + "Expected: " + desiredDataSchema + ". Actual: " + desiredDataSchema);
+ return composeDirectTransferableBlock(responseBlock, desiredDataSchema);
+ }
+
+ /**
+ * Calcite generated {@link DataSchema} should conform with Pinot's group by
result schema thus we only need to check
+ * for correctness similar to distinct case.
+ *
+ * @see
LeafStageTransferableBlockOperator#composeDirectTransferableBlock(InstanceResponseBlock,
DataSchema).
+ * @see
LeafStageTransferableBlockOperator#composeTransferableBlock(InstanceResponseBlock,
DataSchema).
+ */
+ @SuppressWarnings("ConstantConditions")
+ private static TransferableBlock
composeGroupByTransferableBlock(InstanceResponseBlock responseBlock,
+ DataSchema desiredDataSchema) {
+ DataSchema resultSchema = responseBlock.getDataSchema();
+ // GROUP-BY column names conforms with selection expression
+ List<String> selectionColumns =
responseBlock.getQueryContext().getSelectExpressions().stream()
+ .map(e -> e.toString()).collect(Collectors.toList());
+ int[] columnIndices =
SelectionOperatorUtils.getColumnIndices(selectionColumns, resultSchema);
+ Preconditions.checkState(inOrder(columnIndices), "Incompatible group by
result schema for leaf stage."
+ + "Expected: " + desiredDataSchema + ". Actual: " + resultSchema);
+ return composeDirectTransferableBlock(responseBlock, desiredDataSchema);
+ }
+
+
+ /**
+ * Calcite generated {@link DataSchema} should conform with Pinot's agg
result schema thus we only need to check
+ * for correctness similar to distinct case.
+ *
+ * @see
LeafStageTransferableBlockOperator#composeDirectTransferableBlock(InstanceResponseBlock,
DataSchema).
+ * @see
LeafStageTransferableBlockOperator#composeTransferableBlock(InstanceResponseBlock,
DataSchema).
+ */
+ @SuppressWarnings("ConstantConditions")
+ private static TransferableBlock
composeAggregationTransferableBlock(InstanceResponseBlock responseBlock,
+ DataSchema desiredDataSchema) {
+ DataSchema resultSchema = responseBlock.getDataSchema();
+ // AGG-ONLY column names are derived from AggFunction.getColumnName()
+ List<String> selectionColumns =
Arrays.stream(responseBlock.getQueryContext().getAggregationFunctions()).map(
+ a -> a.getColumnName()).collect(Collectors.toList());
+ int[] columnIndices =
SelectionOperatorUtils.getColumnIndices(selectionColumns, resultSchema);
+ Preconditions.checkState(inOrder(columnIndices), "Incompatible aggregate
result schema for leaf stage."
+ + "Expected: " + desiredDataSchema + ". Actual: " + resultSchema);
+ return composeDirectTransferableBlock(responseBlock, desiredDataSchema);
+ }
+
+ /**
+ * Only re-arrange columns to match the projection in the case of select /
order-by, when the desiredDataSchema
+ * doesn't conform with the result block schema exactly.
+ *
+ * @see
LeafStageTransferableBlockOperator#composeTransferableBlock(InstanceResponseBlock,
DataSchema).
+ */
+ @SuppressWarnings("ConstantConditions")
+ private static TransferableBlock
composeSelectTransferableBlock(InstanceResponseBlock responseBlock,
+ DataSchema desiredDataSchema) {
+ DataSchema resultSchema = responseBlock.getDataSchema();
+ List<String> selectionColumns =
SelectionOperatorUtils.getSelectionColumns(responseBlock.getQueryContext(),
+ resultSchema);
+ int[] columnIndices =
SelectionOperatorUtils.getColumnIndices(selectionColumns, resultSchema);
+ if (!inOrder(columnIndices)) {
+ DataSchema adjustedResultSchema =
SelectionOperatorUtils.getSchemaForProjection(resultSchema, columnIndices);
+
Preconditions.checkState(isDataSchemaColumnTypesCompatible(desiredDataSchema.getColumnDataTypes(),
+ adjustedResultSchema.getColumnDataTypes()), "Incompatible result
data schema: "
+ + "Expecting: " + desiredDataSchema + " Actual: " +
adjustedResultSchema);
+ // Extract the result rows
+ Collection<Object[]> resultRows = responseBlock.getRows();
+ List<Object[]> extractedRows = new ArrayList<>(resultRows.size());
+ return composeColumnIndexedTransferableBlock(responseBlock,
adjustedResultSchema, columnIndices);
+ } else {
+ return composeDirectTransferableBlock(responseBlock, desiredDataSchema);
+ }
+ }
+
+ /**
+ * Created {@link TransferableBlock} using column indices.
+ *
+ * @see
LeafStageTransferableBlockOperator#composeTransferableBlock(InstanceResponseBlock,
DataSchema).
+ */
+ private static TransferableBlock
composeColumnIndexedTransferableBlock(InstanceResponseBlock responseBlock,
+ DataSchema desiredDataSchema, int[] columnIndices) {
+ Collection<Object[]> resultRows = responseBlock.getRows();
+ List<Object[]> extractedRows = new ArrayList<>(resultRows.size());
+ if (responseBlock.getQueryContext().getOrderByExpressions() != null) {
+ // extract result row in ordered fashion
+ PriorityQueue<Object[]> priorityQueue = (PriorityQueue<Object[]>)
resultRows;
+ while (!priorityQueue.isEmpty()) {
+ extractedRows.add(canonicalizeRow(priorityQueue.poll(),
desiredDataSchema, columnIndices));
+ }
+ } else {
+ // extract result row in non-ordered fashion
+ for (Object[] row : resultRows) {
+ extractedRows.add(canonicalizeRow(row, desiredDataSchema,
columnIndices));
+ }
+ }
+ return new TransferableBlock(extractedRows, desiredDataSchema,
DataBlock.Type.ROW);
+ }
+
+ /**
+ * Fallback mechanism for {@link TransferableBlock}, used when no special
handling is necessary. This method only
+ * performs {@link DataSchema.ColumnDataType} canonicalization.
+ *
+ * @see
LeafStageTransferableBlockOperator#composeTransferableBlock(InstanceResponseBlock,
DataSchema).
+ */
+ private static TransferableBlock
composeDirectTransferableBlock(InstanceResponseBlock responseBlock,
+ DataSchema desiredDataSchema) {
+ Collection<Object[]> resultRows = responseBlock.getRows();
+ List<Object[]> extractedRows = new ArrayList<>(resultRows.size());
+ if (resultRows instanceof List) {
+ for (Object[] orgRow : resultRows) {
+ extractedRows.add(canonicalizeRow(orgRow, desiredDataSchema));
+ }
+ } else if (resultRows instanceof PriorityQueue) {
+ PriorityQueue<Object[]> priorityQueue = (PriorityQueue<Object[]>)
resultRows;
+ while (!priorityQueue.isEmpty()) {
+ extractedRows.add(canonicalizeRow(priorityQueue.poll(),
desiredDataSchema));
+ }
+ } else {
+ throw new UnsupportedOperationException("Unsupported collection type: "
+ resultRows.getClass());
+ }
+ return new TransferableBlock(extractedRows, desiredDataSchema,
DataBlock.Type.ROW);
+ }
+
+ private static boolean inOrder(int[] columnIndices) {
+ for (int i = 0; i < columnIndices.length; i++) {
+ if (columnIndices[i] != i) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* This util is used to canonicalize row generated from V1 engine, which is
stored using
* {@link DataSchema#getStoredColumnDataTypes()} format. However, the
transferable block ser/de stores data in the
@@ -111,28 +294,35 @@ public class LeafStageTransferableBlockOperator extends
BaseOperator<Transferabl
private static Object[] canonicalizeRow(Object[] row, DataSchema dataSchema)
{
Object[] resultRow = new Object[row.length];
for (int colId = 0; colId < row.length; colId++) {
- resultRow[colId] =
dataSchema.getColumnDataType(colId).convert(row[colId]);
+ Object value = row[colId];
+ if (value != null) {
+ resultRow[colId] = dataSchema.getColumnDataType(colId).convert(value);
+ }
}
return resultRow;
}
- private static List<Object[]> toList(Collection<Object[]> collection,
DataSchema dataSchema) {
- if (collection == null || collection.isEmpty()) {
- return new ArrayList<>();
- }
- List<Object[]> resultRows = new ArrayList<>(collection.size());
- if (collection instanceof List) {
- for (Object[] orgRow : collection) {
- resultRows.add(canonicalizeRow(orgRow, dataSchema));
+ private static Object[] canonicalizeRow(Object[] row, DataSchema dataSchema,
int[] columnIndices) {
+ Object[] resultRow = new Object[columnIndices.length];
+ for (int colId = 0; colId < columnIndices.length; colId++) {
+ Object value = row[columnIndices[colId]];
+ if (value != null) {
+ resultRow[colId] = dataSchema.getColumnDataType(colId).convert(value);
}
- } else if (collection instanceof PriorityQueue) {
- PriorityQueue<Object[]> priorityQueue = (PriorityQueue<Object[]>)
collection;
- while (!priorityQueue.isEmpty()) {
- resultRows.add(canonicalizeRow(priorityQueue.poll(), dataSchema));
+ }
+ return resultRow;
+ }
+
+ private static boolean
isDataSchemaColumnTypesCompatible(DataSchema.ColumnDataType[] desiredTypes,
+ DataSchema.ColumnDataType[] givenTypes) {
+ if (desiredTypes.length != givenTypes.length) {
+ return false;
+ }
+ for (int i = 0; i < desiredTypes.length; i++) {
+ if (desiredTypes[i] != givenTypes[i] &&
!givenTypes[i].isSuperTypeOf(desiredTypes[i])) {
+ return false;
}
- } else {
- throw new UnsupportedOperationException("Unsupported collection type: "
+ collection.getClass());
}
- return resultRows;
+ return true;
}
}
diff --git
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperatorTest.java
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperatorTest.java
index 496a09e4d5..89df15c62b 100644
---
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperatorTest.java
+++
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/LeafStageTransferableBlockOperatorTest.java
@@ -26,28 +26,57 @@ import org.apache.pinot.common.exception.QueryException;
import org.apache.pinot.common.utils.DataSchema;
import org.apache.pinot.core.operator.blocks.InstanceResponseBlock;
import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock;
+import org.apache.pinot.core.query.request.context.QueryContext;
+import
org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils;
import org.apache.pinot.query.runtime.blocks.TransferableBlock;
import org.testng.Assert;
import org.testng.annotations.Test;
+// TODO: add tests for Agg / GroupBy / Distinct result blocks
public class LeafStageTransferableBlockOperatorTest {
@Test
public void shouldReturnDataBlockThenMetadataBlock() {
// Given:
+ QueryContext queryContext =
QueryContextConverterUtils.getQueryContext("SELECT strCol, intCol FROM tbl");
DataSchema schema = new DataSchema(new String[]{"strCol", "intCol"},
new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING,
DataSchema.ColumnDataType.INT});
List<InstanceResponseBlock> resultsBlockList =
Collections.singletonList(new InstanceResponseBlock(
- new SelectionResultsBlock(schema, Arrays.asList(new Object[]{"foo",
1}, new Object[]{"", 2})), null));
+ new SelectionResultsBlock(schema, Arrays.asList(new Object[]{"foo",
1}, new Object[]{"", 2})), queryContext));
LeafStageTransferableBlockOperator operator = new
LeafStageTransferableBlockOperator(resultsBlockList, schema);
// When:
- TransferableBlock transferableBlock = operator.nextBlock();
+ TransferableBlock resultBlock = operator.nextBlock();
// Then:
- Assert.assertEquals(transferableBlock.getContainer().get(0), new
Object[]{"foo", 1});
- Assert.assertEquals(transferableBlock.getContainer().get(1), new
Object[]{"", 2});
+ Assert.assertEquals(resultBlock.getContainer().get(0), new Object[]{"foo",
1});
+ Assert.assertEquals(resultBlock.getContainer().get(1), new Object[]{"",
2});
+ Assert.assertTrue(operator.nextBlock().isEndOfStreamBlock(), "Expected EOS
after reading two rows");
+ }
+
+ @Test
+ public void shouldHandleDesiredDataSchemaConversionCorrectly() {
+ // Given:
+ QueryContext queryContext = QueryContextConverterUtils.getQueryContext(
+ "SELECT boolCol, tsCol, boolCol AS newNamedBoolCol FROM tbl");
+ DataSchema resultSchema = new DataSchema(new String[]{"boolCol", "tsCol"},
+ new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.BOOLEAN,
DataSchema.ColumnDataType.TIMESTAMP});
+ DataSchema desiredSchema = new DataSchema(new String[]{"boolCol", "tsCol",
"newNamedBoolCol"},
+ new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.BOOLEAN,
DataSchema.ColumnDataType.TIMESTAMP,
+ DataSchema.ColumnDataType.BOOLEAN});
+ List<InstanceResponseBlock> resultsBlockList =
Collections.singletonList(new InstanceResponseBlock(
+ new SelectionResultsBlock(resultSchema, Arrays.asList(new Object[]{1,
1660000000000L},
+ new Object[]{0, 1600000000000L})), queryContext));
+ LeafStageTransferableBlockOperator operator = new
LeafStageTransferableBlockOperator(resultsBlockList,
+ desiredSchema);
+
+ // When:
+ TransferableBlock resultBlock = operator.nextBlock();
+
+ // Then:
+ Assert.assertEquals(resultBlock.getContainer().get(0), new Object[]{true,
new Timestamp(1660000000000L), true});
+ Assert.assertEquals(resultBlock.getContainer().get(1), new Object[]{false,
new Timestamp(1600000000000L), false});
Assert.assertTrue(operator.nextBlock().isEndOfStreamBlock(), "Expected EOS
after reading two rows");
}
@@ -55,67 +84,70 @@ public class LeafStageTransferableBlockOperatorTest {
public void shouldHandleCanonicalizationCorrectly() {
// TODO: not all stored types are supported, add additional datatype when
they are supported.
// Given:
+ QueryContext queryContext =
QueryContextConverterUtils.getQueryContext("SELECT boolCol, tsCol FROM tbl");
DataSchema schema = new DataSchema(new String[]{"boolCol", "tsCol"},
new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.BOOLEAN,
DataSchema.ColumnDataType.TIMESTAMP});
List<InstanceResponseBlock> resultsBlockList =
Collections.singletonList(new InstanceResponseBlock(
new SelectionResultsBlock(schema, Arrays.asList(new Object[]{1,
1660000000000L},
- new Object[]{0, 1600000000000L})), null));
+ new Object[]{0, 1600000000000L})), queryContext));
LeafStageTransferableBlockOperator operator = new
LeafStageTransferableBlockOperator(resultsBlockList, schema);
// When:
- TransferableBlock transferableBlock = operator.nextBlock();
+ TransferableBlock resultBlock = operator.nextBlock();
// Then:
- Assert.assertEquals(transferableBlock.getContainer().get(0), new
Object[]{true, new Timestamp(1660000000000L)});
- Assert.assertEquals(transferableBlock.getContainer().get(1), new
Object[]{false, new Timestamp(1600000000000L)});
+ Assert.assertEquals(resultBlock.getContainer().get(0), new Object[]{true,
new Timestamp(1660000000000L)});
+ Assert.assertEquals(resultBlock.getContainer().get(1), new Object[]{false,
new Timestamp(1600000000000L)});
Assert.assertTrue(operator.nextBlock().isEndOfStreamBlock(), "Expected EOS
after reading two rows");
}
@Test
public void shouldReturnMultipleDataBlockThenMetadataBlock() {
// Given:
+ QueryContext queryContext =
QueryContextConverterUtils.getQueryContext("SELECT strCol, intCol FROM tbl");
DataSchema schema = new DataSchema(new String[]{"strCol", "intCol"},
new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING,
DataSchema.ColumnDataType.INT});
List<InstanceResponseBlock> resultsBlockList = Arrays.asList(
new InstanceResponseBlock(new SelectionResultsBlock(schema,
- Arrays.asList(new Object[]{"foo", 1}, new Object[]{"", 2})), null),
+ Arrays.asList(new Object[]{"foo", 1}, new Object[]{"", 2})),
queryContext),
new InstanceResponseBlock(new SelectionResultsBlock(schema,
- Arrays.asList(new Object[]{"bar", 3}, new Object[]{"foo", 4})),
null),
- new InstanceResponseBlock(new SelectionResultsBlock(schema,
Collections.emptyList()), null));
+ Arrays.asList(new Object[]{"bar", 3}, new Object[]{"foo", 4})),
queryContext),
+ new InstanceResponseBlock(new SelectionResultsBlock(schema,
Collections.emptyList()), queryContext));
LeafStageTransferableBlockOperator operator = new
LeafStageTransferableBlockOperator(resultsBlockList, schema);
// When:
- TransferableBlock transferableBlock1 = operator.nextBlock();
- TransferableBlock transferableBlock2 = operator.nextBlock();
- TransferableBlock transferableBlock3 = operator.nextBlock();
+ TransferableBlock resultBlock1 = operator.nextBlock();
+ TransferableBlock resultBlock2 = operator.nextBlock();
+ TransferableBlock resultBlock3 = operator.nextBlock();
// Then:
- Assert.assertEquals(transferableBlock1.getContainer().get(0), new
Object[]{"foo", 1});
- Assert.assertEquals(transferableBlock1.getContainer().get(1), new
Object[]{"", 2});
- Assert.assertEquals(transferableBlock2.getContainer().get(0), new
Object[]{"bar", 3});
- Assert.assertEquals(transferableBlock2.getContainer().get(1), new
Object[]{"foo", 4});
- Assert.assertEquals(transferableBlock3.getContainer().size(), 0);
+ Assert.assertEquals(resultBlock1.getContainer().get(0), new
Object[]{"foo", 1});
+ Assert.assertEquals(resultBlock1.getContainer().get(1), new Object[]{"",
2});
+ Assert.assertEquals(resultBlock2.getContainer().get(0), new
Object[]{"bar", 3});
+ Assert.assertEquals(resultBlock2.getContainer().get(1), new
Object[]{"foo", 4});
+ Assert.assertEquals(resultBlock3.getContainer().size(), 0);
Assert.assertTrue(operator.nextBlock().isEndOfStreamBlock(), "Expected EOS
after reading two rows");
}
@Test
public void shouldGetErrorBlockWhenInstanceResponseContainsError() {
// Given:
+ QueryContext queryContext =
QueryContextConverterUtils.getQueryContext("SELECT strCol, intCol FROM tbl");
DataSchema schema = new DataSchema(new String[]{"strCol", "intCol"},
new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING,
DataSchema.ColumnDataType.INT});
InstanceResponseBlock errorBlock = new InstanceResponseBlock();
errorBlock.addException(QueryException.QUERY_EXECUTION_ERROR.getErrorCode(),
"foobar");
List<InstanceResponseBlock> resultsBlockList = Arrays.asList(
new InstanceResponseBlock(new SelectionResultsBlock(schema,
- Arrays.asList(new Object[]{"foo", 1}, new Object[]{"", 2})), null),
+ Arrays.asList(new Object[]{"foo", 1}, new Object[]{"", 2})),
queryContext),
errorBlock,
- new InstanceResponseBlock(new SelectionResultsBlock(schema,
Collections.emptyList()), null));
+ new InstanceResponseBlock(new SelectionResultsBlock(schema,
Collections.emptyList()), queryContext));
LeafStageTransferableBlockOperator operator = new
LeafStageTransferableBlockOperator(resultsBlockList, schema);
// When:
- TransferableBlock transferableBlock = operator.nextBlock();
+ TransferableBlock resultBlock = operator.nextBlock();
// Then:
- Assert.assertTrue(transferableBlock.isErrorBlock());
+ Assert.assertTrue(resultBlock.isErrorBlock());
}
}
diff --git a/pinot-query-runtime/src/test/resources/queries/Aggregates.json
b/pinot-query-runtime/src/test/resources/queries/Aggregates.json
index b82be2cb5e..cba6bab972 100644
--- a/pinot-query-runtime/src/test/resources/queries/Aggregates.json
+++ b/pinot-query-runtime/src/test/resources/queries/Aggregates.json
@@ -3,19 +3,19 @@
"tables": {
"tbl": {
"schema": [
- { "name": "int_col", "type": "INT" },
- { "name": "double_col", "type": "DOUBLE" },
- { "name": "string_col", "type": "STRING" },
- { "name": "bool_col", "type": "BOOLEAN" }
+ {"name": "int_col", "type": "INT"},
+ {"name": "double_col", "type": "DOUBLE"},
+ {"name": "string_col", "type": "STRING"},
+ {"name": "bool_col", "type": "BOOLEAN"}
],
"inputs": [
- [ 2, 300, "a", true ],
- [ 2, 400, "a", true ],
- [ 3, 100, "b", false ],
- [ 100, 1, "b", false ],
- [ 101, 1.01, "c", false ],
- [ 150, 1.5, "c", false ],
- [ 175, 1.75, "c", true ]
+ [2, 300, "a", true],
+ [2, 400, "a", true],
+ [3, 100, "b", false],
+ [100, 1, "b", false],
+ [101, 1.01, "c", false],
+ [150, 1.5, "c", false],
+ [175, 1.75, "c", true]
]
}
},
@@ -147,21 +147,21 @@
"tables": {
"tbl": {
"schema": [
- { "name": "int_col", "type": "INT" },
- { "name": "double_col", "type": "DOUBLE" },
- { "name": "string_col", "type": "STRING" },
- { "name": "bool_col", "type": "BOOLEAN" }
+ {"name": "int_col", "type": "INT"},
+ {"name": "double_col", "type": "DOUBLE"},
+ {"name": "string_col", "type": "STRING"},
+ {"name": "bool_col", "type": "BOOLEAN"}
],
"inputs": [
- [ 2, 300, "a", false ],
- [ 2, 400, "a", true ],
- [ 3, 100, "b", true ],
- [ 0.001, 1, "b", false ],
- [ 101, 1.01, "c", false ],
- [ 150, 1.5, "c", true ],
- [ 175, 1.75, "c", true ],
- [ -10000, 1.75, "c", false ],
- [ -2, 0.5, "c", false ]
+ [2, 300, "a", false],
+ [2, 400, "a", true],
+ [3, 100, "b", true],
+ [0.001, 1, "b", false],
+ [101, 1.01, "c", false],
+ [150, 1.5, "c", true],
+ [175, 1.75, "c", true],
+ [-10000, 1.75, "c", false],
+ [-2, 0.5, "c", false]
]
}
},
@@ -211,5 +211,48 @@
"sql": "select string_col, count(int_col) from {tbl} a group by
string_col order by string_col having (select count(*) from {tbl} b where
count(int_col) = b.int_col) > 0;"
}
]
+ },
+ "additional_aggregate": {
+ "tables": {
+ "tbl": {
+ "schema": [
+ {"name": "int_col", "type": "INT"},
+ {"name": "double_col", "type": "DOUBLE"},
+ {"name": "string_col", "type": "STRING"},
+ {"name": "bool_col", "type": "BOOLEAN"}
+ ],
+ "inputs": [
+ [2, 300, "a", false],
+ [2, 400, "a", true],
+ [3, 100, "b", true],
+ [0.001, 1, "b", false],
+ [101, 1.01, "c", false],
+ [150, 1.5, "c", true],
+ [175, 1.75, "c", true],
+ [-10000, 1.75, "c", false],
+ [-2, 0.5, "c", false]
+ ]
+ }
+ },
+ "queries": [
+ { "sql": "SELECT min(int_col), min(int_col) FROM {tbl} WHERE int_col <
100" },
+ { "sql": "SELECT min(int_col), count(int_col), count(*) FROM {tbl}" },
+ { "sql": "SELECT string_col, min(int_col), count(int_col), count(*) FROM
{tbl} GROUP BY string_col" },
+ { "sql": "SELECT string_col, bool_col, min(int_col), count(int_col),
count(*) FROM {tbl} GROUP BY string_col, bool_col" },
+ { "sql": "SELECT string_col, bool_col, min(int_col), count(int_col),
count(*) FROM {tbl} GROUP BY bool_col, string_col" },
+ { "sql": "SELECT string_col, string_col AS alias, count(int_col),
count(*) FROM {tbl} GROUP BY bool_col, string_col" },
+ { "sql": "SELECT string_col, bool_col, bool_col AS alias, sum(int_col),
count(int_col), count(*) FROM {tbl} GROUP BY bool_col, string_col" },
+ { "sql": "SELECT sum(int_col), count(int_col) FROM {tbl} GROUP BY
string_col" },
+ { "sql": "SELECT string_col, min(int_col), count(int_col), count(*) FROM
{tbl} GROUP BY string_col ORDER BY string_col" },
+ { "sql": "SELECT string_col, bool_col, min(int_col), count(int_col),
count(*) FROM {tbl} GROUP BY string_col, bool_col ORDER BY string_col" },
+ { "sql": "SELECT string_col, bool_col, min(int_col), count(int_col),
count(*) FROM {tbl} GROUP BY string_col, bool_col ORDER BY bool_col,
string_col" },
+ { "sql": "SELECT string_col, bool_col, min(int_col), count(int_col),
count(*) FROM {tbl} GROUP BY bool_col, string_col ORDER BY bool_col,
string_col" },
+ { "sql": "SELECT string_col, string_col AS alias, count(int_col),
count(*) FROM {tbl} GROUP BY bool_col, string_col ORDER BY string_col" },
+ { "sql": "SELECT string_col, string_col AS alias, count(int_col),
count(*) FROM {tbl} GROUP BY bool_col, string_col ORDER BY bool_col, alias"},
+ { "sql": "SELECT string_col, bool_col, bool_col AS alias, sum(int_col),
count(int_col), count(*) FROM {tbl} GROUP BY bool_col, string_col ORDER BY
alias" },
+ { "sql": "SELECT sum(int_col), count(int_col) FROM {tbl} GROUP BY
string_col ORDER BY string_col" },
+ { "sql": "SELECT * FROM (SELECT string_col, bool_col, min(int_col) AS m,
count(int_col), count(*) AS c FROM {tbl} GROUP BY string_col, bool_col ORDER BY
string_col) WHERE c < m" },
+ { "sql": "SELECT * FROM (SELECT string_col, bool_col, min(int_col) AS m,
count(int_col), count(*) AS c FROM {tbl} GROUP BY string_col, bool_col ORDER BY
bool_col, string_col) WHERE c < m" }
+ ]
}
-}
+}
\ No newline at end of file
diff --git a/pinot-query-runtime/src/test/resources/queries/Distincts.json
b/pinot-query-runtime/src/test/resources/queries/Distincts.json
new file mode 100644
index 0000000000..22ee05725c
--- /dev/null
+++ b/pinot-query-runtime/src/test/resources/queries/Distincts.json
@@ -0,0 +1,47 @@
+{
+ "general_distinct": {
+ "tables": {
+ "tbl": {
+ "schema": [
+ { "name": "intCol", "type": "INT" },
+ { "name": "doubleCol", "type": "DOUBLE" },
+ { "name": "strCol", "type": "STRING" },
+ { "name": "boolCol", "type": "BOOLEAN" }
+ ],
+ "inputs": [
+ [ 2, 300, "a", true ],
+ [ 2, 400, "a", true ],
+ [ 3, 100, "b", false ],
+ [ 100, 1, "b", false ],
+ [ 101, 1.01, "c", false ],
+ [ 150, 1.5, "c", false ],
+ [ 175, 1.75, "c", true ]
+ ]
+ }
+ },
+ "queries": [
+ { "sql": "SELECT DISTINCT intCol FROM {tbl}" },
+ { "sql": "SELECT intCol FROM {tbl} GROUP BY intCol" },
+ { "sql": "SELECT DISTINCT strCol, boolCol FROM {tbl}" },
+ { "sql": "SELECT strCol, boolCol FROM {tbl} GROUP BY strCol, boolCol" },
+ { "sql": "SELECT strCol, boolCol FROM {tbl} GROUP BY boolCol, strCol" },
+ { "sql": "SELECT strCol FROM {tbl} GROUP BY strCol, boolCol" },
+ { "sql": "SELECT boolCol FROM {tbl} GROUP BY strCol, boolCol" },
+ { "sql": "SELECT DISTINCT intCol, doubleCol FROM {tbl} ORDER BY
doubleCol" },
+ { "sql": "SELECT intCol FROM {tbl} GROUP BY intCol ORDER BY intCol" },
+ { "sql": "SELECT DISTINCT strCol, boolCol, intCol FROM {tbl} ORDER BY
intCol" },
+ { "sql": "SELECT strCol, boolCol FROM {tbl} GROUP BY strCol, boolCol
ORDER BY boolCol" },
+ { "sql": "SELECT strCol, boolCol FROM {tbl} GROUP BY boolCol, strCol
ORDER BY strCol" },
+ { "sql": "SELECT strCol FROM {tbl} GROUP BY strCol, boolCol ORDER BY
boolCol" },
+ { "sql": "SELECT strCol FROM (SELECT strCol FROM {tbl} GROUP BY
boolCol, strCol ORDER BY strCol) WHERE strCol <> 'foo'" },
+ { "sql": "SELECT intCol FROM (SELECT DISTINCT intCol FROM {tbl}) WHERE
intCol < 100" },
+ { "sql": "SELECT intCol FROM (SELECT intCol FROM {tbl} GROUP BY intCol)
WHERE intCol < 100" },
+ { "sql": "SELECT strCol FROM (SELECT DISTINCT strCol, boolCol FROM
{tbl}) WHERE boolCol = false" },
+ { "sql": "SELECT strCol FROM (SELECT strCol, boolCol FROM {tbl} GROUP
BY strCol, boolCol) WHERE strCol <> 'foo'" },
+ { "sql": "SELECT strCol, boolCol FROM (SELECT strCol, boolCol FROM
{tbl} GROUP BY boolCol, strCol) WHERE strCol <> 'foo'" },
+ { "sql": "SELECT strCol FROM (SELECT strCol FROM {tbl} GROUP BY strCol,
boolCol) WHERE strCol != 'lol'" },
+ { "sql": "SELECT intCol FROM (SELECT DISTINCT intCol, doubleCol FROM
{tbl} ORDER BY doubleCol) WHERE doubleCol < 100" },
+ { "sql": "SELECT strCol FROM (SELECT DISTINCT strCol, boolCol, intCol
FROM {tbl} ORDER BY intCol) WHERE intCol > 10" }
+ ]
+ }
+}
diff --git
a/pinot-query-runtime/src/test/resources/queries/LexicalStructure.json
b/pinot-query-runtime/src/test/resources/queries/LexicalStructure.json
index 400ed57fac..ea6e139ddc 100644
--- a/pinot-query-runtime/src/test/resources/queries/LexicalStructure.json
+++ b/pinot-query-runtime/src/test/resources/queries/LexicalStructure.json
@@ -135,11 +135,22 @@
"inputs": [["1"]]
}
},
- "queries": [{
- "psql": "4.1.2.6",
- "description": "numeric constant supports",
- "sql": "SELECT data, 42, 3.5, 4., .001, 5e2, 1.925e-3 FROM {tbl}"
- }]
+ "queries": [
+ {
+ "ignored": true,
+ "comments": "numeric constant without specific type will cast all
floating points into BIG DECIMAL which is not supported by type-hoisting on
leaf stage",
+ "psql": "4.1.2.6",
+ "description": "numeric constant supports",
+ "sql": "SELECT data, 42, 3.5, 4., .001, 5e2, 1.925e-3 FROM {tbl}"
+ },
+ {
+ "ignored": true,
+ "comments": "floating point comparison missing precision checker",
+ "psql": "4.1.2.6",
+ "description": "numeric constant supports",
+ "sql": "SELECT data, 42, CAST(3.5 AS FLOAT), CAST(4. AS FLOAT),
CAST(.001 AS FLOAT), CAST(5e2 AS FLOAT), CAST(1.925e-3 AS FLOAT) FROM {tbl}"
+ }
+ ]
},
"constant_casting": {
"tables": {
diff --git
a/pinot-query-runtime/src/test/resources/queries/SelectExpressions.json
b/pinot-query-runtime/src/test/resources/queries/SelectExpressions.json
index f214307b2a..af925382ef 100644
--- a/pinot-query-runtime/src/test/resources/queries/SelectExpressions.json
+++ b/pinot-query-runtime/src/test/resources/queries/SelectExpressions.json
@@ -37,7 +37,20 @@
{ "sql": "SELECT intCol as \"from\" FROM {tbl1}"},
{ "sql": "SELECT intCol as key, SUM(doubleCol + floatCol) AS aggSum FROM
{tbl1} GROUP BY intCol"},
{ "sql": "SELECT intCol, SUM(avgVal) FROM (SELECT strCol, intCol,
AVG(doubleCol) AS avgVal FROM {tbl1} GROUP BY intCol, strCol) GROUP BY intCol"},
- { "sql": "SELECT strCol, MAX(sumVal), MAX(sumVal + avgVal) AS transVal
FROM (SELECT strCol, intCol, SUM(floatCol + 2 * intCol) AS sumVal,
AVG(doubleCol) AS avgVal FROM {tbl1} GROUP BY strCol, intCol) GROUP BY strCol
ORDER BY MAX(sumVal)" }
+ { "sql": "SELECT strCol, MAX(sumVal), MAX(sumVal + avgVal) AS transVal
FROM (SELECT strCol, intCol, SUM(floatCol + 2 * intCol) AS sumVal,
AVG(doubleCol) AS avgVal FROM {tbl1} GROUP BY strCol, intCol) GROUP BY strCol
ORDER BY MAX(sumVal)" },
+ { "sql": "SELECT intCol, intCol FROM {tbl1} WHERE intCol < 100" },
+ { "sql": "SELECT intCol, intCol, doubleCol, strCol, strCol FROM {tbl1}"
},
+ { "sql": "SELECT intCol, intCol FROM {tbl1} WHERE intCol < 100 ORDER BY
doubleCol" },
+ { "sql": "SELECT intCol, intCol FROM {tbl1} WHERE intCol < 100 ORDER BY
intCol" },
+ { "sql": "SELECT intCol, intCol, doubleCol, strCol, strCol FROM {tbl1}
ORDER BY intCol" },
+ { "sql": "SELECT intCol, intCol, doubleCol, strCol, strCol FROM {tbl1}
ORDER BY strCol, intCol" },
+ { "sql": "SELECT intCol, intCol, doubleCol, strCol, strCol FROM {tbl1}
ORDER BY floatCol, intCol" },
+ { "sql": "SELECT intCol, intCol, doubleCol, strCol, strCol FROM {tbl1}
ORDER BY floatCol" },
+
+ { "sql": "SELECT intCol + alias FROM (SELECT intCol, intCol as alias
FROM {tbl1}) WHERE intCol < 100" },
+ { "sql": "SELECT intCol, intCol, doubleCol, strCol, strCol FROM {tbl1}"
},
+ { "sql": "SELECT {tbl1}.intCol, {tbl1}.intCol, {tbl1}.doubleCol,
{tbl2}.strCol, {tbl2}.strCol FROM {tbl1}, {tbl2} WHERE {tbl1}.intCol =
{tbl2}.intCol" },
+ { "sql": "SELECT {tbl2}.intCol, {tbl2}.intCol FROM {tbl1}, {tbl2} WHERE
{tbl1}.intCol = {tbl2}.intCol AND {tbl1}.intCol < 100 ORDER BY
{tbl1}.doubleCol" }
]
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]