xiangfu0 commented on code in PR #19990: URL: https://github.com/apache/pinot/pull/19990#discussion_r4371824504
########## pinot-core/src/test/java/org/apache/pinot/queries/AnyValueQueriesTest.java: ########## @@ -0,0 +1,229 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.pinot.queries; + +import java.io.File; +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.response.broker.ResultTable; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + + +/// Queries test for ANY_VALUE, whose data type argument the optimizer supplies from the schema when the +/// `inferAnyValueType` query option is set. All the rows with the same key hold the same values, so that any value is +/// the expected one. +public class AnyValueQueriesTest extends BaseQueriesTest { + private static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "AnyValueQueriesTest"); + private static final String RAW_TABLE_NAME = "testTable"; + private static final String SEGMENT_NAME = "testSegment"; + private static final int NUM_RECORDS = 10; + + private static final Schema SCHEMA = new Schema.SchemaBuilder().setSchemaName(RAW_TABLE_NAME) + .addSingleValueDimension("keyCol", DataType.INT) + .addSingleValueDimension("intCol", DataType.INT) + .addSingleValueDimension("longCol", DataType.LONG) + .addSingleValueDimension("floatCol", DataType.FLOAT) + .addSingleValueDimension("doubleCol", DataType.DOUBLE) + .addMetric("bigDecimalCol", DataType.BIG_DECIMAL) + .addSingleValueDimension("boolCol", DataType.BOOLEAN) + .addSingleValueDimension("timestampCol", DataType.TIMESTAMP) + .addSingleValueDimension("stringCol", DataType.STRING) + .addSingleValueDimension("jsonCol", DataType.JSON) + .addSingleValueDimension("bytesCol", DataType.BYTES) + .addSingleValueDimension("uuidCol", DataType.UUID) + .build(); + private static final TableConfig TABLE_CONFIG = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setNoDictionaryColumns(List.of("longCol", "doubleCol", "bytesCol")).build(); + private static final String INFER_ANY_VALUE_TYPE = "SET inferAnyValueType=true; "; + private static final String ALL_COLUMNS = "ANY_VALUE(intCol), ANY_VALUE(longCol), ANY_VALUE(floatCol), " + + "ANY_VALUE(doubleCol), ANY_VALUE(bigDecimalCol), ANY_VALUE(boolCol), ANY_VALUE(timestampCol), " + + "ANY_VALUE(stringCol), ANY_VALUE(jsonCol), ANY_VALUE(bytesCol), ANY_VALUE(uuidCol)"; + + private IndexSegment _indexSegment; + private List<IndexSegment> _indexSegments; + + @Override + protected String getFilter() { + return ""; + } + + @Override + protected IndexSegment getIndexSegment() { + return _indexSegment; + } + + @Override + protected List<IndexSegment> getIndexSegments() { + return _indexSegments; + } + + @BeforeClass + public void setUp() + throws Exception { + FileUtils.deleteDirectory(INDEX_DIR); + + List<GenericRow> records = new ArrayList<>(NUM_RECORDS); + for (int i = 0; i < NUM_RECORDS; i++) { + int key = i % 2; + GenericRow record = new GenericRow(); + record.putValue("keyCol", key); + record.putValue("intCol", 10 + key); + record.putValue("longCol", 10_000_000_000L + key); + record.putValue("floatCol", 0.5f + key); + record.putValue("doubleCol", 0.25 + key); + record.putValue("bigDecimalCol", new BigDecimal("12345678901234567890.5").add(BigDecimal.valueOf(key))); + record.putValue("boolCol", key == 0); + record.putValue("timestampCol", 1_600_000_000_000L + key); + record.putValue("stringCol", "s" + key); + record.putValue("jsonCol", "{\"k\":" + key + "}"); + record.putValue("bytesCol", new byte[]{(byte) key, 1}); + record.putValue("uuidCol", "123e4567-e89b-12d3-a456-42661417400" + key); + records.add(record); + } + + SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(TABLE_CONFIG, SCHEMA); + segmentGeneratorConfig.setTableName(RAW_TABLE_NAME); + segmentGeneratorConfig.setSegmentName(SEGMENT_NAME); + segmentGeneratorConfig.setOutDir(INDEX_DIR.getPath()); + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + driver.init(segmentGeneratorConfig, new GenericRowRecordReader(records)); + driver.build(); + + ImmutableSegment immutableSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), ReadMode.mmap); + _indexSegment = immutableSegment; + _indexSegments = List.of(immutableSegment, immutableSegment); + } + + @Test + public void testAggregationOnly() { + BrokerResponseNative brokerResponse = getBrokerResponseForOptimizedQuery( + INFER_ANY_VALUE_TYPE + "SELECT " + ALL_COLUMNS + " FROM testTable WHERE keyCol = 0", SCHEMA); + DataSchema expectedDataSchema = new DataSchema(new String[]{ + "anyvalue(intCol,'INT')", "anyvalue(longCol,'LONG')", "anyvalue(floatCol,'FLOAT')", + "anyvalue(doubleCol,'DOUBLE')", "anyvalue(bigDecimalCol,'BIG_DECIMAL')", "anyvalue(boolCol,'BOOLEAN')", + "anyvalue(timestampCol,'TIMESTAMP')", "anyvalue(stringCol,'STRING')", "anyvalue(jsonCol,'STRING')", + "anyvalue(bytesCol,'BYTES')", "anyvalue(uuidCol,'UUID')" + }, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.FLOAT, ColumnDataType.DOUBLE, + ColumnDataType.BIG_DECIMAL, ColumnDataType.BOOLEAN, ColumnDataType.TIMESTAMP, ColumnDataType.STRING, + ColumnDataType.STRING, ColumnDataType.BYTES, ColumnDataType.UUID + }); + Object[] expectedRow = new Object[]{ + 10, 10_000_000_000L, 0.5f, 0.25, "12345678901234567890.5", true, + new Timestamp(1_600_000_000_000L).toString(), "s0", "{\"k\":0}", "0001", "123e4567-e89b-12d3-a456-426614174000" + }; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, + new ResultTable(expectedDataSchema, List.<Object[]>of(expectedRow))); + } + + @Test + public void testGroupBy() { + ResultTable resultTable = getBrokerResponseForOptimizedQuery( + INFER_ANY_VALUE_TYPE + "SELECT keyCol, ANY_VALUE(intCol) AS v1, ANY_VALUE(boolCol) AS v2, " + + "ANY_VALUE(timestampCol) AS v3, " + + "ANY_VALUE(uuidCol) AS v4 FROM testTable GROUP BY keyCol ORDER BY ANY_VALUE(longCol) DESC", + SCHEMA).getResultTable(); + assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"keyCol", "v1", "v2", "v3", "v4"}, + new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.INT, ColumnDataType.BOOLEAN, ColumnDataType.TIMESTAMP, + ColumnDataType.UUID + })); + List<Object[]> rows = resultTable.getRows(); + assertEquals(rows.size(), 2); + assertEquals(rows.get(0), new Object[]{ + 1, 11, false, new Timestamp(1_600_000_000_001L).toString(), "123e4567-e89b-12d3-a456-426614174001" + }); + assertEquals(rows.get(1), new Object[]{ + 0, 10, true, new Timestamp(1_600_000_000_000L).toString(), "123e4567-e89b-12d3-a456-426614174000" + }); + } + + @Test + public void testEmptyInput() { + ResultTable resultTable = getBrokerResponseForOptimizedQuery( + INFER_ANY_VALUE_TYPE + "SELECT " + ALL_COLUMNS + " FROM testTable WHERE keyCol < 0", SCHEMA).getResultTable(); + assertEquals(resultTable.getDataSchema().getColumnDataTypes(), new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.FLOAT, ColumnDataType.DOUBLE, + ColumnDataType.BIG_DECIMAL, ColumnDataType.BOOLEAN, ColumnDataType.TIMESTAMP, ColumnDataType.STRING, + ColumnDataType.STRING, ColumnDataType.BYTES, ColumnDataType.UUID + }); + assertEquals(resultTable.getRows().size(), 1); + assertEquals(resultTable.getRows().get(0), new Object[11]); + + resultTable = getBrokerResponseForOptimizedQuery( + INFER_ANY_VALUE_TYPE + "SELECT keyCol, ANY_VALUE(boolCol), ANY_VALUE(timestampCol) FROM testTable " + + "WHERE keyCol < 0 GROUP BY keyCol", + SCHEMA).getResultTable(); + assertEquals(resultTable.getDataSchema().getColumnDataTypes(), + new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.BOOLEAN, ColumnDataType.TIMESTAMP}); + assertTrue(resultTable.getRows().isEmpty()); + } + + @Test + public void testExplicitDataType() { Review Comment: Worth a comment here flagging the deliberate SSE/MSE divergence: SSE accepts the explicit mismatched form (`ANY_VALUE(intCol, 'STRING')` reads INT as STRING), while MSE rejects it in `checkAnyValueDataTypeArgument`. It's intentional and documented in the PR, but a one-liner here would save a future reader the surprise. ########## pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotAggregateTypeArgumentRewriteRule.java: ########## @@ -158,7 +186,24 @@ private static String getDataTypeArgument(AggregateCall aggCall, RelNode input) return "UUID"; default: throw new IllegalArgumentException( - String.format("Unsupported data type for %s(dataColumn, timeColumn): %s", name, dataType)); + String.format("Unsupported data type for %s(%s): %s", name, arguments, dataType)); Review Comment: Nit: when the expr type itself is unsupported (e.g. an ARRAY) on an explicit 2-arg call, `inferDataType` throws first with the `(expr)` message rather than the `(expr, 'dataType')` one, so the error won't mention the user's explicit arg. Harmless, just slightly less precise. ########## pinot-core/src/test/java/org/apache/pinot/queries/AnyValueQueriesTest.java: ########## @@ -0,0 +1,229 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.pinot.queries; + +import java.io.File; +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.response.broker.ResultTable; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + + +/// Queries test for ANY_VALUE, whose data type argument the optimizer supplies from the schema when the +/// `inferAnyValueType` query option is set. All the rows with the same key hold the same values, so that any value is +/// the expected one. +public class AnyValueQueriesTest extends BaseQueriesTest { + private static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "AnyValueQueriesTest"); + private static final String RAW_TABLE_NAME = "testTable"; + private static final String SEGMENT_NAME = "testSegment"; + private static final int NUM_RECORDS = 10; + + private static final Schema SCHEMA = new Schema.SchemaBuilder().setSchemaName(RAW_TABLE_NAME) + .addSingleValueDimension("keyCol", DataType.INT) + .addSingleValueDimension("intCol", DataType.INT) + .addSingleValueDimension("longCol", DataType.LONG) + .addSingleValueDimension("floatCol", DataType.FLOAT) + .addSingleValueDimension("doubleCol", DataType.DOUBLE) + .addMetric("bigDecimalCol", DataType.BIG_DECIMAL) + .addSingleValueDimension("boolCol", DataType.BOOLEAN) + .addSingleValueDimension("timestampCol", DataType.TIMESTAMP) + .addSingleValueDimension("stringCol", DataType.STRING) + .addSingleValueDimension("jsonCol", DataType.JSON) + .addSingleValueDimension("bytesCol", DataType.BYTES) + .addSingleValueDimension("uuidCol", DataType.UUID) + .build(); + private static final TableConfig TABLE_CONFIG = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setNoDictionaryColumns(List.of("longCol", "doubleCol", "bytesCol")).build(); + private static final String INFER_ANY_VALUE_TYPE = "SET inferAnyValueType=true; "; + private static final String ALL_COLUMNS = "ANY_VALUE(intCol), ANY_VALUE(longCol), ANY_VALUE(floatCol), " + + "ANY_VALUE(doubleCol), ANY_VALUE(bigDecimalCol), ANY_VALUE(boolCol), ANY_VALUE(timestampCol), " + + "ANY_VALUE(stringCol), ANY_VALUE(jsonCol), ANY_VALUE(bytesCol), ANY_VALUE(uuidCol)"; + + private IndexSegment _indexSegment; + private List<IndexSegment> _indexSegments; + + @Override + protected String getFilter() { + return ""; + } + + @Override + protected IndexSegment getIndexSegment() { + return _indexSegment; + } + + @Override + protected List<IndexSegment> getIndexSegments() { + return _indexSegments; + } + + @BeforeClass + public void setUp() + throws Exception { + FileUtils.deleteDirectory(INDEX_DIR); + + List<GenericRow> records = new ArrayList<>(NUM_RECORDS); + for (int i = 0; i < NUM_RECORDS; i++) { + int key = i % 2; + GenericRow record = new GenericRow(); + record.putValue("keyCol", key); + record.putValue("intCol", 10 + key); + record.putValue("longCol", 10_000_000_000L + key); + record.putValue("floatCol", 0.5f + key); + record.putValue("doubleCol", 0.25 + key); + record.putValue("bigDecimalCol", new BigDecimal("12345678901234567890.5").add(BigDecimal.valueOf(key))); + record.putValue("boolCol", key == 0); + record.putValue("timestampCol", 1_600_000_000_000L + key); + record.putValue("stringCol", "s" + key); + record.putValue("jsonCol", "{\"k\":" + key + "}"); + record.putValue("bytesCol", new byte[]{(byte) key, 1}); + record.putValue("uuidCol", "123e4567-e89b-12d3-a456-42661417400" + key); + records.add(record); + } + + SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(TABLE_CONFIG, SCHEMA); + segmentGeneratorConfig.setTableName(RAW_TABLE_NAME); + segmentGeneratorConfig.setSegmentName(SEGMENT_NAME); + segmentGeneratorConfig.setOutDir(INDEX_DIR.getPath()); + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + driver.init(segmentGeneratorConfig, new GenericRowRecordReader(records)); + driver.build(); + + ImmutableSegment immutableSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), ReadMode.mmap); + _indexSegment = immutableSegment; + _indexSegments = List.of(immutableSegment, immutableSegment); + } + + @Test + public void testAggregationOnly() { + BrokerResponseNative brokerResponse = getBrokerResponseForOptimizedQuery( + INFER_ANY_VALUE_TYPE + "SELECT " + ALL_COLUMNS + " FROM testTable WHERE keyCol = 0", SCHEMA); + DataSchema expectedDataSchema = new DataSchema(new String[]{ + "anyvalue(intCol,'INT')", "anyvalue(longCol,'LONG')", "anyvalue(floatCol,'FLOAT')", + "anyvalue(doubleCol,'DOUBLE')", "anyvalue(bigDecimalCol,'BIG_DECIMAL')", "anyvalue(boolCol,'BOOLEAN')", + "anyvalue(timestampCol,'TIMESTAMP')", "anyvalue(stringCol,'STRING')", "anyvalue(jsonCol,'STRING')", + "anyvalue(bytesCol,'BYTES')", "anyvalue(uuidCol,'UUID')" + }, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.FLOAT, ColumnDataType.DOUBLE, + ColumnDataType.BIG_DECIMAL, ColumnDataType.BOOLEAN, ColumnDataType.TIMESTAMP, ColumnDataType.STRING, + ColumnDataType.STRING, ColumnDataType.BYTES, ColumnDataType.UUID + }); + Object[] expectedRow = new Object[]{ + 10, 10_000_000_000L, 0.5f, 0.25, "12345678901234567890.5", true, + new Timestamp(1_600_000_000_000L).toString(), "s0", "{\"k\":0}", "0001", "123e4567-e89b-12d3-a456-426614174000" + }; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, + new ResultTable(expectedDataSchema, List.<Object[]>of(expectedRow))); + } + + @Test + public void testGroupBy() { + ResultTable resultTable = getBrokerResponseForOptimizedQuery( + INFER_ANY_VALUE_TYPE + "SELECT keyCol, ANY_VALUE(intCol) AS v1, ANY_VALUE(boolCol) AS v2, " + + "ANY_VALUE(timestampCol) AS v3, " + + "ANY_VALUE(uuidCol) AS v4 FROM testTable GROUP BY keyCol ORDER BY ANY_VALUE(longCol) DESC", + SCHEMA).getResultTable(); + assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"keyCol", "v1", "v2", "v3", "v4"}, + new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.INT, ColumnDataType.BOOLEAN, ColumnDataType.TIMESTAMP, + ColumnDataType.UUID + })); + List<Object[]> rows = resultTable.getRows(); + assertEquals(rows.size(), 2); + assertEquals(rows.get(0), new Object[]{ + 1, 11, false, new Timestamp(1_600_000_000_001L).toString(), "123e4567-e89b-12d3-a456-426614174001" + }); + assertEquals(rows.get(1), new Object[]{ + 0, 10, true, new Timestamp(1_600_000_000_000L).toString(), "123e4567-e89b-12d3-a456-426614174000" + }); + } + + @Test + public void testEmptyInput() { + ResultTable resultTable = getBrokerResponseForOptimizedQuery( + INFER_ANY_VALUE_TYPE + "SELECT " + ALL_COLUMNS + " FROM testTable WHERE keyCol < 0", SCHEMA).getResultTable(); + assertEquals(resultTable.getDataSchema().getColumnDataTypes(), new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.FLOAT, ColumnDataType.DOUBLE, + ColumnDataType.BIG_DECIMAL, ColumnDataType.BOOLEAN, ColumnDataType.TIMESTAMP, ColumnDataType.STRING, + ColumnDataType.STRING, ColumnDataType.BYTES, ColumnDataType.UUID + }); + assertEquals(resultTable.getRows().size(), 1); + assertEquals(resultTable.getRows().get(0), new Object[11]); + + resultTable = getBrokerResponseForOptimizedQuery( + INFER_ANY_VALUE_TYPE + "SELECT keyCol, ANY_VALUE(boolCol), ANY_VALUE(timestampCol) FROM testTable " + + "WHERE keyCol < 0 GROUP BY keyCol", + SCHEMA).getResultTable(); + assertEquals(resultTable.getDataSchema().getColumnDataTypes(), + new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.BOOLEAN, ColumnDataType.TIMESTAMP}); + assertTrue(resultTable.getRows().isEmpty()); + } + + @Test + public void testExplicitDataType() { Review Comment: Worth a comment here flagging the deliberate SSE/MSE divergence: SSE accepts the explicit mismatched form (`ANY_VALUE(intCol, 'STRING')` reads INT as STRING), while MSE rejects it in `checkAnyValueDataTypeArgument`. It's intentional and documented in the PR, but a one-liner here would save a future reader the surprise. ########## pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotAggregateTypeArgumentRewriteRule.java: ########## @@ -158,7 +186,24 @@ private static String getDataTypeArgument(AggregateCall aggCall, RelNode input) return "UUID"; default: throw new IllegalArgumentException( - String.format("Unsupported data type for %s(dataColumn, timeColumn): %s", name, dataType)); + String.format("Unsupported data type for %s(%s): %s", name, arguments, dataType)); Review Comment: Nit: when the expr type itself is unsupported (e.g. an ARRAY) on an explicit 2-arg call, `inferDataType` throws first with the `(expr)` message rather than the `(expr, 'dataType')` one, so the error won't mention the user's explicit arg. Harmless, just slightly less precise. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
