This is an automated email from the ASF dual-hosted git repository.
xiangfu0 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 0513b741343 Support BYTES array literals end to end (#19247)
0513b741343 is described below
commit 0513b7413439f5d5a5c4318a6cbec93e0b9b398e
Author: Xiang Fu <[email protected]>
AuthorDate: Tue Aug 25 16:58:44 2026 -0700
Support BYTES array literals end to end (#19247)
* Support BYTES array literals end to end
Pinot can ingest and project multi-value BYTES columns, but neither query
engine could reliably construct an equivalent SQL literal. Single-stage parsing
lacked a native MV BYTES representation, while multi-stage execution returned
external arrays where DataBlock expects ByteArray[].
Add the Thrift literal arm and the conversions needed by both engines. Keep
ordinary single-stage broker-to-server requests encoded as
arrayValueConstructor with scalar binary literals so older Literal readers can
still decode them. Servers still need the new execution support before using
the feature, but mixed-version decoding remains compatible.
Cache constant multi-stage byte-array operands in both internal and
external form to avoid per-row allocation. Exercise Avro array<bytes>
ingestion, dictionary and raw segment storage, literal projection, and
arraysOverlap through both query engines.
* Test BYTES array runtime conversion
Directly exercise TypeUtils conversion from external byte[][] values to the
internal ByteArray[] representation required for multi-stage block
serialization. Cover empty, single-byte, multi-byte, and unsigned byte content.
* Optimize dynamic BYTES array construction
Use a BYTES_ARRAY-specific transform operand for mixed literal and dynamic
values. Reuse existing ByteArray wrappers and backing bytes while allocating
only the required outer result array per row.
* Use generic dispatch for BYTES array construction
FunctionOperand already converts stored values to the external UDF
representation. Let ArrayFunctions build byte[][] through that shared path,
then normalize the result back to internal ByteArray[] in TypeUtils instead of
maintaining a one-off operand and factory branch.
* Address review feedback for BYTES array literals
Make BYTES array conversion explicit across Rex and scalar paths, reject
unsupported elements consistently, and cover broker, wire, and end-to-end
review cases.
* Use native Thrift literal for BYTES arrays
* Follow array literal conventions
* Address BYTES array review feedback
---
.../LiteralOnlyBrokerRequestTest.java | 31 +++
.../common/function/scalar/ArrayFunctions.java | 7 +
.../org/apache/pinot/common/request/Literal.java | 257 ++++++++++++++-------
.../common/request/context/LiteralContext.java | 27 ++-
.../org/apache/pinot/common/utils/DataSchema.java | 11 +
.../pinot/common/utils/request/RequestUtils.java | 39 ++++
.../common/function/scalar/ArrayFunctionsTest.java | 34 +++
.../pinot/common/request/LiteralSerDeTest.java | 168 ++++++++++++++
.../common/request/context/LiteralContextTest.java | 44 ++++
.../apache/pinot/common/utils/DataSchemaTest.java | 17 ++
.../common/utils/request/RequestUtilsTest.java | 22 ++
pinot-common/src/thrift/query.thrift | 1 +
.../function/ArrayLiteralTransformFunction.java | 46 +++-
.../ArrayLiteralTransformFunctionTest.java | 25 ++
.../integration/tests/custom/BytesMvTypeTest.java | 61 +++++
.../query/parser/CalciteRexExpressionParser.java | 11 +-
.../parser/CalciteRexExpressionParserTest.java | 13 ++
.../planner/serde/RexExpressionSerDeTest.java | 9 +-
.../runtime/operator/TransformOperatorTest.java | 52 +++++
19 files changed, 784 insertions(+), 91 deletions(-)
diff --git
a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java
b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java
index b3154c5bb90..5db658610dc 100644
---
a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java
+++
b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java
@@ -80,6 +80,37 @@ public class LiteralOnlyBrokerRequestTest {
assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT
1, '2', 3 FROM myTable")));
}
+ @Test
+ public void testArrayLiteralBrokerRequestFromSQL()
+ throws Exception {
+ SingleConnectionBrokerRequestHandler requestHandler =
+ new SingleConnectionBrokerRequestHandler(new PinotConfiguration(),
"testBrokerId",
+ new BrokerRequestIdGenerator(), null, ACCESS_CONTROL_FACTORY,
null, null, null, null,
+ mock(ServerRoutingStatsManager.class), mock(FailureDetector.class),
+ ThreadAccountantUtils.getNoOpAccountant(), null, null);
+
+ BrokerResponse brokerResponse = requestHandler.handleRequest(
+ "SELECT ARRAY[1, 2] AS ints, ARRAY['one', 'two'] AS strings");
+ ResultTable resultTable = brokerResponse.getResultTable();
+ assertEquals(resultTable.getDataSchema().getColumnDataType(0),
DataSchema.ColumnDataType.INT_ARRAY);
+ assertEquals(resultTable.getDataSchema().getColumnDataType(1),
DataSchema.ColumnDataType.STRING_ARRAY);
+ assertEquals((int[]) resultTable.getRows().get(0)[0], new int[]{1, 2});
+ assertEquals((String[]) resultTable.getRows().get(0)[1], new
String[]{"one", "two"});
+
+ brokerResponse = requestHandler.handleRequest("SELECT ARRAY[X'00',
X'0102'] AS bytes");
+ resultTable = brokerResponse.getResultTable();
+ assertEquals(resultTable.getDataSchema().getColumnName(0), "bytes");
+ assertEquals(resultTable.getDataSchema().getColumnDataType(0),
DataSchema.ColumnDataType.BYTES_ARRAY);
+ assertEquals(resultTable.getRows().size(), 1);
+ assertEquals(resultTable.getRows().get(0), new Object[]{new String[]{"00",
"0102"}});
+
+ brokerResponse = requestHandler.handleRequest(
+ "SELECT ARRAYS_OVERLAP(ARRAY[X'00', X'0102'], ARRAY[X'03', X'0102'])
AS overlaps");
+ resultTable = brokerResponse.getResultTable();
+ assertEquals(resultTable.getDataSchema().getColumnDataType(0),
DataSchema.ColumnDataType.BOOLEAN);
+ assertEquals(resultTable.getRows().get(0)[0], true);
+ }
+
@Test
public void testLiteralOnlyTransformBrokerRequestFromSQL() {
assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT
now()")));
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java
index 2aa1a7c076a..a6db5b7906f 100644
---
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java
+++
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java
@@ -352,6 +352,13 @@ public class ArrayFunctions {
}
return strArr;
}
+ if (clazz == byte[].class) {
+ byte[][] bytesArr = new byte[arr.length][];
+ for (int i = 0; i < arr.length; i++) {
+ bytesArr[i] = (byte[]) arr[i];
+ }
+ return bytesArr;
+ }
return arr;
}
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java
b/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java
index ca15bb5f87d..6f084d7f1a5 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/request/Literal.java
@@ -25,7 +25,7 @@
package org.apache.pinot.common.request;
@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
[email protected](value = "Autogenerated by Thrift Compiler
(0.21.0)", date = "2025-04-16")
[email protected](value = "Autogenerated by Thrift Compiler
(0.21.0)", date = "2026-08-13")
public class Literal extends org.apache.thrift.TUnion<Literal,
Literal._Fields> {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new
org.apache.thrift.protocol.TStruct("Literal");
private static final org.apache.thrift.protocol.TField BOOL_VALUE_FIELD_DESC
= new org.apache.thrift.protocol.TField("boolValue",
org.apache.thrift.protocol.TType.BOOL, (short)1);
@@ -44,6 +44,7 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
private static final org.apache.thrift.protocol.TField
FLOAT_ARRAY_VALUE_FIELD_DESC = new
org.apache.thrift.protocol.TField("floatArrayValue",
org.apache.thrift.protocol.TType.LIST, (short)14);
private static final org.apache.thrift.protocol.TField
DOUBLE_ARRAY_VALUE_FIELD_DESC = new
org.apache.thrift.protocol.TField("doubleArrayValue",
org.apache.thrift.protocol.TType.LIST, (short)15);
private static final org.apache.thrift.protocol.TField
STRING_ARRAY_VALUE_FIELD_DESC = new
org.apache.thrift.protocol.TField("stringArrayValue",
org.apache.thrift.protocol.TType.LIST, (short)16);
+ private static final org.apache.thrift.protocol.TField
BYTES_ARRAY_VALUE_FIELD_DESC = new
org.apache.thrift.protocol.TField("bytesArrayValue",
org.apache.thrift.protocol.TType.LIST, (short)17);
/** The set of fields this struct contains, along with convenience methods
for finding and manipulating them. */
public enum _Fields implements org.apache.thrift.TFieldIdEnum {
@@ -62,7 +63,8 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
LONG_ARRAY_VALUE((short)13, "longArrayValue"),
FLOAT_ARRAY_VALUE((short)14, "floatArrayValue"),
DOUBLE_ARRAY_VALUE((short)15, "doubleArrayValue"),
- STRING_ARRAY_VALUE((short)16, "stringArrayValue");
+ STRING_ARRAY_VALUE((short)16, "stringArrayValue"),
+ BYTES_ARRAY_VALUE((short)17, "bytesArrayValue");
private static final java.util.Map<java.lang.String, _Fields> byName = new
java.util.HashMap<java.lang.String, _Fields>();
@@ -110,6 +112,8 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
return DOUBLE_ARRAY_VALUE;
case 16: // STRING_ARRAY_VALUE
return STRING_ARRAY_VALUE;
+ case 17: // BYTES_ARRAY_VALUE
+ return BYTES_ARRAY_VALUE;
default:
return null;
}
@@ -192,6 +196,9 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
tmpMap.put(_Fields.STRING_ARRAY_VALUE, new
org.apache.thrift.meta_data.FieldMetaData("stringArrayValue",
org.apache.thrift.TFieldRequirementType.OPTIONAL,
new
org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST,
new
org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))));
+ tmpMap.put(_Fields.BYTES_ARRAY_VALUE, new
org.apache.thrift.meta_data.FieldMetaData("bytesArrayValue",
org.apache.thrift.TFieldRequirementType.OPTIONAL,
+ new
org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST,
+ new
org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING
, true))));
metaDataMap = java.util.Collections.unmodifiableMap(tmpMap);
org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Literal.class,
metaDataMap);
}
@@ -320,6 +327,12 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
return x;
}
+ public static Literal bytesArrayValue(java.util.List<java.nio.ByteBuffer>
value) {
+ Literal x = new Literal();
+ x.setBytesArrayValue(value);
+ return x;
+ }
+
@Override
protected void checkType(_Fields setField, java.lang.Object value) throws
java.lang.ClassCastException {
@@ -404,6 +417,11 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
break;
}
throw new java.lang.ClassCastException("Was expecting value of type
java.util.List<java.lang.String> for field 'stringArrayValue', but got " +
value.getClass().getSimpleName());
+ case BYTES_ARRAY_VALUE:
+ if (value instanceof java.util.List) {
+ break;
+ }
+ throw new java.lang.ClassCastException("Was expecting value of type
java.util.List<java.nio.ByteBuffer> for field 'bytesArrayValue', but got " +
value.getClass().getSimpleName());
default:
throw new java.lang.IllegalArgumentException("Unknown field id " +
setField);
}
@@ -517,13 +535,13 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
if (field.type == INT_ARRAY_VALUE_FIELD_DESC.type) {
java.util.List<java.lang.Integer> intArrayValue;
{
- org.apache.thrift.protocol.TList _list44 = iprot.readListBegin();
- intArrayValue = new
java.util.ArrayList<java.lang.Integer>(_list44.size);
- int _elem45;
- for (int _i46 = 0; _i46 < _list44.size; ++_i46)
+ org.apache.thrift.protocol.TList _list60 = iprot.readListBegin();
+ intArrayValue = new
java.util.ArrayList<java.lang.Integer>(_list60.size);
+ int _elem61;
+ for (int _i62 = 0; _i62 < _list60.size; ++_i62)
{
- _elem45 = iprot.readI32();
- intArrayValue.add(_elem45);
+ _elem61 = iprot.readI32();
+ intArrayValue.add(_elem61);
}
iprot.readListEnd();
}
@@ -536,13 +554,13 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
if (field.type == LONG_ARRAY_VALUE_FIELD_DESC.type) {
java.util.List<java.lang.Long> longArrayValue;
{
- org.apache.thrift.protocol.TList _list47 = iprot.readListBegin();
- longArrayValue = new
java.util.ArrayList<java.lang.Long>(_list47.size);
- long _elem48;
- for (int _i49 = 0; _i49 < _list47.size; ++_i49)
+ org.apache.thrift.protocol.TList _list63 = iprot.readListBegin();
+ longArrayValue = new
java.util.ArrayList<java.lang.Long>(_list63.size);
+ long _elem64;
+ for (int _i65 = 0; _i65 < _list63.size; ++_i65)
{
- _elem48 = iprot.readI64();
- longArrayValue.add(_elem48);
+ _elem64 = iprot.readI64();
+ longArrayValue.add(_elem64);
}
iprot.readListEnd();
}
@@ -555,13 +573,13 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
if (field.type == FLOAT_ARRAY_VALUE_FIELD_DESC.type) {
java.util.List<java.lang.Integer> floatArrayValue;
{
- org.apache.thrift.protocol.TList _list50 = iprot.readListBegin();
- floatArrayValue = new
java.util.ArrayList<java.lang.Integer>(_list50.size);
- int _elem51;
- for (int _i52 = 0; _i52 < _list50.size; ++_i52)
+ org.apache.thrift.protocol.TList _list66 = iprot.readListBegin();
+ floatArrayValue = new
java.util.ArrayList<java.lang.Integer>(_list66.size);
+ int _elem67;
+ for (int _i68 = 0; _i68 < _list66.size; ++_i68)
{
- _elem51 = iprot.readI32();
- floatArrayValue.add(_elem51);
+ _elem67 = iprot.readI32();
+ floatArrayValue.add(_elem67);
}
iprot.readListEnd();
}
@@ -574,13 +592,13 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
if (field.type == DOUBLE_ARRAY_VALUE_FIELD_DESC.type) {
java.util.List<java.lang.Double> doubleArrayValue;
{
- org.apache.thrift.protocol.TList _list53 = iprot.readListBegin();
- doubleArrayValue = new
java.util.ArrayList<java.lang.Double>(_list53.size);
- double _elem54;
- for (int _i55 = 0; _i55 < _list53.size; ++_i55)
+ org.apache.thrift.protocol.TList _list69 = iprot.readListBegin();
+ doubleArrayValue = new
java.util.ArrayList<java.lang.Double>(_list69.size);
+ double _elem70;
+ for (int _i71 = 0; _i71 < _list69.size; ++_i71)
{
- _elem54 = iprot.readDouble();
- doubleArrayValue.add(_elem54);
+ _elem70 = iprot.readDouble();
+ doubleArrayValue.add(_elem70);
}
iprot.readListEnd();
}
@@ -593,13 +611,13 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
if (field.type == STRING_ARRAY_VALUE_FIELD_DESC.type) {
java.util.List<java.lang.String> stringArrayValue;
{
- org.apache.thrift.protocol.TList _list56 = iprot.readListBegin();
- stringArrayValue = new
java.util.ArrayList<java.lang.String>(_list56.size);
- @org.apache.thrift.annotation.Nullable java.lang.String _elem57;
- for (int _i58 = 0; _i58 < _list56.size; ++_i58)
+ org.apache.thrift.protocol.TList _list72 = iprot.readListBegin();
+ stringArrayValue = new
java.util.ArrayList<java.lang.String>(_list72.size);
+ @org.apache.thrift.annotation.Nullable java.lang.String _elem73;
+ for (int _i74 = 0; _i74 < _list72.size; ++_i74)
{
- _elem57 = iprot.readString();
- stringArrayValue.add(_elem57);
+ _elem73 = iprot.readString();
+ stringArrayValue.add(_elem73);
}
iprot.readListEnd();
}
@@ -608,6 +626,25 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
return null;
}
+ case BYTES_ARRAY_VALUE:
+ if (field.type == BYTES_ARRAY_VALUE_FIELD_DESC.type) {
+ java.util.List<java.nio.ByteBuffer> bytesArrayValue;
+ {
+ org.apache.thrift.protocol.TList _list75 = iprot.readListBegin();
+ bytesArrayValue = new
java.util.ArrayList<java.nio.ByteBuffer>(_list75.size);
+ @org.apache.thrift.annotation.Nullable java.nio.ByteBuffer
_elem76;
+ for (int _i77 = 0; _i77 < _list75.size; ++_i77)
+ {
+ _elem76 = iprot.readBinary();
+ bytesArrayValue.add(_elem76);
+ }
+ iprot.readListEnd();
+ }
+ return bytesArrayValue;
+ } else {
+ org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+ return null;
+ }
default:
throw new java.lang.IllegalStateException("setField wasn't null, but
didn't match any of the case statements!");
}
@@ -668,9 +705,9 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
java.util.List<java.lang.Integer> intArrayValue =
(java.util.List<java.lang.Integer>)value_;
{
oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I32,
intArrayValue.size()));
- for (int _iter59 : intArrayValue)
+ for (int _iter78 : intArrayValue)
{
- oprot.writeI32(_iter59);
+ oprot.writeI32(_iter78);
}
oprot.writeListEnd();
}
@@ -679,9 +716,9 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
java.util.List<java.lang.Long> longArrayValue =
(java.util.List<java.lang.Long>)value_;
{
oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I64,
longArrayValue.size()));
- for (long _iter60 : longArrayValue)
+ for (long _iter79 : longArrayValue)
{
- oprot.writeI64(_iter60);
+ oprot.writeI64(_iter79);
}
oprot.writeListEnd();
}
@@ -690,9 +727,9 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
java.util.List<java.lang.Integer> floatArrayValue =
(java.util.List<java.lang.Integer>)value_;
{
oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I32,
floatArrayValue.size()));
- for (int _iter61 : floatArrayValue)
+ for (int _iter80 : floatArrayValue)
{
- oprot.writeI32(_iter61);
+ oprot.writeI32(_iter80);
}
oprot.writeListEnd();
}
@@ -701,9 +738,9 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
java.util.List<java.lang.Double> doubleArrayValue =
(java.util.List<java.lang.Double>)value_;
{
oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.DOUBLE,
doubleArrayValue.size()));
- for (double _iter62 : doubleArrayValue)
+ for (double _iter81 : doubleArrayValue)
{
- oprot.writeDouble(_iter62);
+ oprot.writeDouble(_iter81);
}
oprot.writeListEnd();
}
@@ -712,9 +749,20 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
java.util.List<java.lang.String> stringArrayValue =
(java.util.List<java.lang.String>)value_;
{
oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRING,
stringArrayValue.size()));
- for (java.lang.String _iter63 : stringArrayValue)
+ for (java.lang.String _iter82 : stringArrayValue)
+ {
+ oprot.writeString(_iter82);
+ }
+ oprot.writeListEnd();
+ }
+ return;
+ case BYTES_ARRAY_VALUE:
+ java.util.List<java.nio.ByteBuffer> bytesArrayValue =
(java.util.List<java.nio.ByteBuffer>)value_;
+ {
+ oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRING,
bytesArrayValue.size()));
+ for (java.nio.ByteBuffer _iter83 : bytesArrayValue)
{
- oprot.writeString(_iter63);
+ oprot.writeBinary(_iter83);
}
oprot.writeListEnd();
}
@@ -776,13 +824,13 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
case INT_ARRAY_VALUE:
java.util.List<java.lang.Integer> intArrayValue;
{
- org.apache.thrift.protocol.TList _list64 = iprot.readListBegin();
- intArrayValue = new
java.util.ArrayList<java.lang.Integer>(_list64.size);
- int _elem65;
- for (int _i66 = 0; _i66 < _list64.size; ++_i66)
+ org.apache.thrift.protocol.TList _list84 = iprot.readListBegin();
+ intArrayValue = new
java.util.ArrayList<java.lang.Integer>(_list84.size);
+ int _elem85;
+ for (int _i86 = 0; _i86 < _list84.size; ++_i86)
{
- _elem65 = iprot.readI32();
- intArrayValue.add(_elem65);
+ _elem85 = iprot.readI32();
+ intArrayValue.add(_elem85);
}
iprot.readListEnd();
}
@@ -790,13 +838,13 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
case LONG_ARRAY_VALUE:
java.util.List<java.lang.Long> longArrayValue;
{
- org.apache.thrift.protocol.TList _list67 = iprot.readListBegin();
- longArrayValue = new
java.util.ArrayList<java.lang.Long>(_list67.size);
- long _elem68;
- for (int _i69 = 0; _i69 < _list67.size; ++_i69)
+ org.apache.thrift.protocol.TList _list87 = iprot.readListBegin();
+ longArrayValue = new
java.util.ArrayList<java.lang.Long>(_list87.size);
+ long _elem88;
+ for (int _i89 = 0; _i89 < _list87.size; ++_i89)
{
- _elem68 = iprot.readI64();
- longArrayValue.add(_elem68);
+ _elem88 = iprot.readI64();
+ longArrayValue.add(_elem88);
}
iprot.readListEnd();
}
@@ -804,13 +852,13 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
case FLOAT_ARRAY_VALUE:
java.util.List<java.lang.Integer> floatArrayValue;
{
- org.apache.thrift.protocol.TList _list70 = iprot.readListBegin();
- floatArrayValue = new
java.util.ArrayList<java.lang.Integer>(_list70.size);
- int _elem71;
- for (int _i72 = 0; _i72 < _list70.size; ++_i72)
+ org.apache.thrift.protocol.TList _list90 = iprot.readListBegin();
+ floatArrayValue = new
java.util.ArrayList<java.lang.Integer>(_list90.size);
+ int _elem91;
+ for (int _i92 = 0; _i92 < _list90.size; ++_i92)
{
- _elem71 = iprot.readI32();
- floatArrayValue.add(_elem71);
+ _elem91 = iprot.readI32();
+ floatArrayValue.add(_elem91);
}
iprot.readListEnd();
}
@@ -818,13 +866,13 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
case DOUBLE_ARRAY_VALUE:
java.util.List<java.lang.Double> doubleArrayValue;
{
- org.apache.thrift.protocol.TList _list73 = iprot.readListBegin();
- doubleArrayValue = new
java.util.ArrayList<java.lang.Double>(_list73.size);
- double _elem74;
- for (int _i75 = 0; _i75 < _list73.size; ++_i75)
+ org.apache.thrift.protocol.TList _list93 = iprot.readListBegin();
+ doubleArrayValue = new
java.util.ArrayList<java.lang.Double>(_list93.size);
+ double _elem94;
+ for (int _i95 = 0; _i95 < _list93.size; ++_i95)
{
- _elem74 = iprot.readDouble();
- doubleArrayValue.add(_elem74);
+ _elem94 = iprot.readDouble();
+ doubleArrayValue.add(_elem94);
}
iprot.readListEnd();
}
@@ -832,17 +880,31 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
case STRING_ARRAY_VALUE:
java.util.List<java.lang.String> stringArrayValue;
{
- org.apache.thrift.protocol.TList _list76 = iprot.readListBegin();
- stringArrayValue = new
java.util.ArrayList<java.lang.String>(_list76.size);
- @org.apache.thrift.annotation.Nullable java.lang.String _elem77;
- for (int _i78 = 0; _i78 < _list76.size; ++_i78)
+ org.apache.thrift.protocol.TList _list96 = iprot.readListBegin();
+ stringArrayValue = new
java.util.ArrayList<java.lang.String>(_list96.size);
+ @org.apache.thrift.annotation.Nullable java.lang.String _elem97;
+ for (int _i98 = 0; _i98 < _list96.size; ++_i98)
{
- _elem77 = iprot.readString();
- stringArrayValue.add(_elem77);
+ _elem97 = iprot.readString();
+ stringArrayValue.add(_elem97);
}
iprot.readListEnd();
}
return stringArrayValue;
+ case BYTES_ARRAY_VALUE:
+ java.util.List<java.nio.ByteBuffer> bytesArrayValue;
+ {
+ org.apache.thrift.protocol.TList _list99 = iprot.readListBegin();
+ bytesArrayValue = new
java.util.ArrayList<java.nio.ByteBuffer>(_list99.size);
+ @org.apache.thrift.annotation.Nullable java.nio.ByteBuffer
_elem100;
+ for (int _i101 = 0; _i101 < _list99.size; ++_i101)
+ {
+ _elem100 = iprot.readBinary();
+ bytesArrayValue.add(_elem100);
+ }
+ iprot.readListEnd();
+ }
+ return bytesArrayValue;
default:
throw new java.lang.IllegalStateException("setField wasn't null, but
didn't match any of the case statements!");
}
@@ -902,9 +964,9 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
java.util.List<java.lang.Integer> intArrayValue =
(java.util.List<java.lang.Integer>)value_;
{
oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I32,
intArrayValue.size()));
- for (int _iter79 : intArrayValue)
+ for (int _iter102 : intArrayValue)
{
- oprot.writeI32(_iter79);
+ oprot.writeI32(_iter102);
}
oprot.writeListEnd();
}
@@ -913,9 +975,9 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
java.util.List<java.lang.Long> longArrayValue =
(java.util.List<java.lang.Long>)value_;
{
oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I64,
longArrayValue.size()));
- for (long _iter80 : longArrayValue)
+ for (long _iter103 : longArrayValue)
{
- oprot.writeI64(_iter80);
+ oprot.writeI64(_iter103);
}
oprot.writeListEnd();
}
@@ -924,9 +986,9 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
java.util.List<java.lang.Integer> floatArrayValue =
(java.util.List<java.lang.Integer>)value_;
{
oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.I32,
floatArrayValue.size()));
- for (int _iter81 : floatArrayValue)
+ for (int _iter104 : floatArrayValue)
{
- oprot.writeI32(_iter81);
+ oprot.writeI32(_iter104);
}
oprot.writeListEnd();
}
@@ -935,9 +997,9 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
java.util.List<java.lang.Double> doubleArrayValue =
(java.util.List<java.lang.Double>)value_;
{
oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.DOUBLE,
doubleArrayValue.size()));
- for (double _iter82 : doubleArrayValue)
+ for (double _iter105 : doubleArrayValue)
{
- oprot.writeDouble(_iter82);
+ oprot.writeDouble(_iter105);
}
oprot.writeListEnd();
}
@@ -946,9 +1008,20 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
java.util.List<java.lang.String> stringArrayValue =
(java.util.List<java.lang.String>)value_;
{
oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRING,
stringArrayValue.size()));
- for (java.lang.String _iter83 : stringArrayValue)
+ for (java.lang.String _iter106 : stringArrayValue)
{
- oprot.writeString(_iter83);
+ oprot.writeString(_iter106);
+ }
+ oprot.writeListEnd();
+ }
+ return;
+ case BYTES_ARRAY_VALUE:
+ java.util.List<java.nio.ByteBuffer> bytesArrayValue =
(java.util.List<java.nio.ByteBuffer>)value_;
+ {
+ oprot.writeListBegin(new
org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRING,
bytesArrayValue.size()));
+ for (java.nio.ByteBuffer _iter107 : bytesArrayValue)
+ {
+ oprot.writeBinary(_iter107);
}
oprot.writeListEnd();
}
@@ -993,6 +1066,8 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
return DOUBLE_ARRAY_VALUE_FIELD_DESC;
case STRING_ARRAY_VALUE:
return STRING_ARRAY_VALUE_FIELD_DESC;
+ case BYTES_ARRAY_VALUE:
+ return BYTES_ARRAY_VALUE_FIELD_DESC;
default:
throw new java.lang.IllegalArgumentException("Unknown field id " +
setField);
}
@@ -1243,6 +1318,19 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
value_ =
java.util.Objects.requireNonNull(value,"_Fields.STRING_ARRAY_VALUE");
}
+ public java.util.List<java.nio.ByteBuffer> getBytesArrayValue() {
+ if (getSetField() == _Fields.BYTES_ARRAY_VALUE) {
+ return (java.util.List<java.nio.ByteBuffer>)getFieldValue();
+ } else {
+ throw new java.lang.RuntimeException("Cannot get field 'bytesArrayValue'
because union is currently set to " + getFieldDesc(getSetField()).name);
+ }
+ }
+
+ public void setBytesArrayValue(java.util.List<java.nio.ByteBuffer> value) {
+ setField_ = _Fields.BYTES_ARRAY_VALUE;
+ value_ =
java.util.Objects.requireNonNull(value,"_Fields.BYTES_ARRAY_VALUE");
+ }
+
public boolean isSetBoolValue() {
return setField_ == _Fields.BOOL_VALUE;
}
@@ -1323,6 +1411,11 @@ public class Literal extends
org.apache.thrift.TUnion<Literal, Literal._Fields>
}
+ public boolean isSetBytesArrayValue() {
+ return setField_ == _Fields.BYTES_ARRAY_VALUE;
+ }
+
+
public boolean equals(java.lang.Object other) {
if (other instanceof Literal) {
return equals((Literal)other);
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/request/context/LiteralContext.java
b/pinot-common/src/main/java/org/apache/pinot/common/request/context/LiteralContext.java
index 3f5b09f0274..3f99fc36e60 100644
---
a/pinot-common/src/main/java/org/apache/pinot/common/request/context/LiteralContext.java
+++
b/pinot-common/src/main/java/org/apache/pinot/common/request/context/LiteralContext.java
@@ -29,6 +29,7 @@ import org.apache.pinot.common.request.Literal;
import org.apache.pinot.common.utils.request.RequestUtils;
import org.apache.pinot.spi.data.FieldSpec.DataType;
import org.apache.pinot.spi.utils.BigDecimalUtils;
+import org.apache.pinot.spi.utils.BytesUtils;
import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder;
import org.apache.pinot.spi.utils.PinotDataType;
@@ -135,6 +136,11 @@ public class LiteralContext {
_value = RequestUtils.getStringArrayValue(literal);
_pinotDataType = PinotDataType.STRING_ARRAY;
break;
+ case BYTES_ARRAY_VALUE:
+ _type = DataType.BYTES;
+ _value = RequestUtils.getBytesArrayValue(literal);
+ _pinotDataType = PinotDataType.BYTES_ARRAY;
+ break;
default:
throw new IllegalStateException("Unsupported field type: " +
literal.getSetField());
}
@@ -147,15 +153,18 @@ public class LiteralContext {
_pinotDataType = getPinotDataType(type, value);
}
- // TODO: Revisit MV support for BOOLEAN, BIG_DECIMAL, BYTES and UUID.
+ // TODO: Revisit MV support for BOOLEAN, BIG_DECIMAL and UUID.
+ // https://github.com/apache/pinot/issues/19338
@Nullable
private static PinotDataType getPinotDataType(DataType type, @Nullable
Object value) {
if (value == null) {
return null;
}
if (type == DataType.BYTES) {
- Preconditions.checkState(value.getClass().getComponentType() ==
byte.class, "Bytes array is not supported");
- return PinotDataType.BYTES;
+ Class<?> componentType = value.getClass().getComponentType();
+ Preconditions.checkState(componentType == byte.class || componentType ==
byte[].class,
+ "Expected byte[] or byte[][], got: %s", value.getClass());
+ return componentType == byte.class ? PinotDataType.BYTES :
PinotDataType.BYTES_ARRAY;
}
boolean singleValue = !value.getClass().isArray();
switch (type) {
@@ -297,7 +306,7 @@ public class LiteralContext {
@Override
public int hashCode() {
- return Objects.hash(_value, _type);
+ return Arrays.deepHashCode(new Object[]{_value, _type});
}
@Override
@@ -309,7 +318,7 @@ public class LiteralContext {
return false;
}
LiteralContext that = (LiteralContext) o;
- return _type.equals(that._type) && Objects.equals(_value, that._value);
+ return _type.equals(that._type) && Objects.deepEquals(_value, that._value);
}
@Override
@@ -333,6 +342,14 @@ public class LiteralContext {
return "'" + Arrays.toString((double[]) _value) + "'";
case STRING_ARRAY:
return "'" + Arrays.toString((String[]) _value) + "'";
+ case BYTES_ARRAY: {
+ byte[][] bytesArray = (byte[][]) _value;
+ String[] hexValues = new String[bytesArray.length];
+ for (int i = 0; i < bytesArray.length; i++) {
+ hexValues[i] = BytesUtils.toHexString(bytesArray[i]);
+ }
+ return "'" + Arrays.toString(hexValues) + "'";
+ }
default:
throw new IllegalStateException("Unsupported PinotDataType: " +
_pinotDataType);
}
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/utils/DataSchema.java
b/pinot-common/src/main/java/org/apache/pinot/common/utils/DataSchema.java
index 52312c2a741..6d5e25b7704 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/utils/DataSchema.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/DataSchema.java
@@ -589,6 +589,17 @@ public class DataSchema {
if (value instanceof Timestamp[]) {
return fromTimestampArray((Timestamp[]) value);
}
+ if (value instanceof byte[][]) {
+ byte[][] bytesArray = (byte[][]) value;
+ ByteArray[] internalBytesArray = new ByteArray[bytesArray.length];
+ for (int i = 0; i < bytesArray.length; i++) {
+ internalBytesArray[i] = new ByteArray(bytesArray[i]);
+ }
+ return internalBytesArray;
+ }
+ if (value instanceof UUID[]) {
+ return fromUuidArray(value);
+ }
return value;
default:
return value;
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java
b/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java
index 20e1765d0f1..0f5a3b4d0c6 100644
---
a/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java
+++
b/pinot-common/src/main/java/org/apache/pinot/common/utils/request/RequestUtils.java
@@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import java.math.BigDecimal;
+import java.nio.ByteBuffer;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
@@ -179,6 +180,14 @@ public class RequestUtils {
return Literal.stringArrayValue(Arrays.asList(value));
}
+ public static Literal getLiteral(byte[][] value) {
+ List<ByteBuffer> bytesArray = new ArrayList<>(value.length);
+ for (byte[] bytes : value) {
+ bytesArray.add(ByteBuffer.wrap(bytes.clone()));
+ }
+ return Literal.bytesArrayValue(bytesArray);
+ }
+
public static Literal getLiteral(@Nullable Object object) {
if (object == null) {
return getNullLiteral();
@@ -228,6 +237,9 @@ public class RequestUtils {
if (object instanceof String[]) {
return getLiteral((String[]) object);
}
+ if (object instanceof byte[][]) {
+ return getLiteral((byte[][]) object);
+ }
return getLiteral(object.toString());
}
@@ -255,6 +267,9 @@ public class RequestUtils {
case BOOLEAN:
literal.setBoolValue(node.booleanValue());
break;
+ case BINARY:
+ literal.setBinaryValue(node.getValueAs(byte[].class));
+ break;
case NULL:
literal.setNullValue(true);
break;
@@ -329,6 +344,10 @@ public class RequestUtils {
return getLiteralExpression(getLiteral(value));
}
+ public static Expression getLiteralExpression(byte[][] value) {
+ return getLiteralExpression(getLiteral(value));
+ }
+
public static Expression getLiteralExpression(SqlLiteral node) {
return getLiteralExpression(getLiteral(node));
}
@@ -370,6 +389,8 @@ public class RequestUtils {
return getDoubleArrayValue(literal);
case STRING_ARRAY_VALUE:
return getStringArrayValue(literal);
+ case BYTES_ARRAY_VALUE:
+ return getBytesArrayValue(literal);
default:
throw new IllegalStateException("Unsupported field type: " + type);
}
@@ -419,6 +440,19 @@ public class RequestUtils {
return literal.getStringArrayValue().toArray(new String[0]);
}
+ public static byte[][] getBytesArrayValue(Literal literal) {
+ List<ByteBuffer> list = literal.getBytesArrayValue();
+ int size = list.size();
+ byte[][] array = new byte[size][];
+ for (int i = 0; i < size; i++) {
+ ByteBuffer buffer = list.get(i).duplicate();
+ byte[] bytes = new byte[buffer.remaining()];
+ buffer.get(bytes);
+ array[i] = bytes;
+ }
+ return array;
+ }
+
public static Pair<ColumnDataType, Object> getLiteralTypeAndValue(Literal
literal) {
Literal._Fields type = literal.getSetField();
switch (type) {
@@ -450,6 +484,8 @@ public class RequestUtils {
return Pair.of(ColumnDataType.DOUBLE_ARRAY,
getDoubleArrayValue(literal));
case STRING_ARRAY_VALUE:
return Pair.of(ColumnDataType.STRING_ARRAY,
getStringArrayValue(literal));
+ case BYTES_ARRAY_VALUE:
+ return Pair.of(ColumnDataType.BYTES_ARRAY,
getBytesArrayValue(literal));
default:
throw new IllegalStateException("Unsupported field type: " + type);
}
@@ -659,6 +695,9 @@ public class RequestUtils {
case STRING_ARRAY_VALUE:
return literal.getStringArrayValue().stream().map(value -> "'" + value
+ "'").collect(Collectors.toList())
.toString();
+ case BYTES_ARRAY_VALUE:
+ return Arrays.stream(getBytesArrayValue(literal)).map(value -> "X'" +
BytesUtils.toHexString(value) + "'")
+ .collect(Collectors.toList()).toString();
default:
throw new IllegalStateException("Unsupported field type: " + type);
}
diff --git
a/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/ArrayFunctionsTest.java
b/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/ArrayFunctionsTest.java
new file mode 100644
index 00000000000..70ad0c2a334
--- /dev/null
+++
b/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/ArrayFunctionsTest.java
@@ -0,0 +1,34 @@
+/**
+ * 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.common.function.scalar;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+
+/// Tests array construction behavior that depends on runtime Java element
types.
+public class ArrayFunctionsTest {
+
+ @Test
+ public void testBytesArrayValueConstructor() {
+ byte[][] expected = {{0}, {1, 2}};
+
+ Assert.assertEquals(ArrayFunctions.arrayValueConstructor(expected[0],
expected[1]), expected);
+ }
+}
diff --git
a/pinot-common/src/test/java/org/apache/pinot/common/request/LiteralSerDeTest.java
b/pinot-common/src/test/java/org/apache/pinot/common/request/LiteralSerDeTest.java
new file mode 100644
index 00000000000..1056f9a11ba
--- /dev/null
+++
b/pinot-common/src/test/java/org/apache/pinot/common/request/LiteralSerDeTest.java
@@ -0,0 +1,168 @@
+/**
+ * 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.common.request;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.pinot.common.utils.request.RequestUtils;
+import org.apache.pinot.sql.parsers.CalciteSqlParser;
+import org.apache.thrift.TDeserializer;
+import org.apache.thrift.TException;
+import org.apache.thrift.TSerializer;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TCompactProtocol;
+import org.apache.thrift.protocol.TProtocolFactory;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+
+/// Verifies Thrift literal conversion and wire serialization.
+public class LiteralSerDeTest {
+ private static final short BYTES_ARRAY_FIELD_ID = 17;
+
+ @Test
+ public void testBytesArrayRoundTrip()
+ throws TException {
+ List<byte[][]> values = List.of(new byte[0][],
+ new byte[][]{{}, {0}, {1, 2}, {(byte) 0xff}});
+ List<TProtocolFactory> protocolFactories =
+ List.of(new TCompactProtocol.Factory(), new TBinaryProtocol.Factory());
+
+ for (TProtocolFactory protocolFactory : protocolFactories) {
+ for (byte[][] expected : values) {
+ PinotQuery query = new PinotQuery();
+
query.setSelectList(List.of(RequestUtils.getLiteralExpression(expected)));
+
+ byte[] serialized = new TSerializer(protocolFactory).serialize(query);
+ PinotQuery deserialized = new PinotQuery();
+ new TDeserializer(protocolFactory).deserialize(deserialized,
serialized);
+
+ Literal literal = deserialized.getSelectList().get(0).getLiteral();
+ assertTrue(literal.isSetBytesArrayValue());
+ assertEquals(literal.getSetField().getThriftFieldId(),
BYTES_ARRAY_FIELD_ID);
+ assertEquals(RequestUtils.getBytesArrayValue(literal), expected);
+ }
+ }
+ }
+
+ @Test
+ public void testBytesArrayConversionRespectsByteBufferBounds() {
+ ByteBuffer sliced = ByteBuffer.wrap(new byte[]{9, 0, 1, 2, 9});
+ sliced.position(1);
+ sliced.limit(4);
+ ByteBuffer direct = ByteBuffer.allocateDirect(2);
+ direct.put(new byte[]{3, 4});
+ direct.flip();
+
+ Literal literal = Literal.bytesArrayValue(List.of(sliced, direct));
+
+ assertEquals(RequestUtils.getBytesArrayValue(literal), new byte[][]{{0, 1,
2}, {3, 4}});
+ assertEquals(sliced.position(), 1);
+ assertEquals(direct.position(), 0);
+ }
+
+ @Test
+ public void testBytesArrayDeepCopy() {
+ Literal literal = RequestUtils.getLiteral(new byte[][]{{0}, {1, 2}});
+ Literal copy = literal.deepCopy();
+
+ assertEquals(copy, literal);
+ literal.getBytesArrayValue().get(0).put(0, (byte) 9);
+ assertEquals(RequestUtils.getBytesArrayValue(copy), new byte[][]{{0}, {1,
2}});
+ }
+
+ @Test
+ public void testSingleStageQueryUsesNativeBytesArrayLiteral()
+ throws TException {
+ for (String sql : List.of("SELECT ARRAY[X'00', X'0102'] FROM myTable",
+ "SELECT id FROM myTable WHERE ARRAYS_OVERLAP(bytesMV, ARRAY[X'01'])",
+ "SELECT ARRAY[X'02'], COUNT(*) FROM myTable GROUP BY ARRAY[X'02']",
+ "SELECT COUNT(*) FROM myTable HAVING
ARRAYS_OVERLAP(ARRAYAGG(bytesColumn, 'BYTES'), ARRAY[X'03'])",
+ "SELECT id FROM myTable "
+ + "ORDER BY CASE WHEN ARRAYS_OVERLAP(bytesMV, ARRAY[X'04']) THEN 1
ELSE 0 END")) {
+ PinotQuery query = CalciteSqlParser.compileToPinotQuery(sql);
+ assertTrue(containsBytesArrayLiteral(query));
+
+ byte[] serialized = new TSerializer(new
TCompactProtocol.Factory()).serialize(query);
+ PinotQuery deserialized = new PinotQuery();
+ new TDeserializer(new
TCompactProtocol.Factory()).deserialize(deserialized, serialized);
+ assertTrue(containsBytesArrayLiteral(deserialized));
+ }
+
+ PinotQuery nested = CalciteSqlParser.compileToPinotQuery(
+ "SELECT ARRAY_LENGTH(ARRAY[X'05', X'0607']) FROM myTable");
+ assertEquals(nested.getSelectList().get(0).getLiteral().getIntValue(), 2);
+ }
+
+ @Test
+ public void testExistingLiteralArmsStillRoundTrip()
+ throws TException {
+ for (Literal literal : List.of(RequestUtils.getLiteral(new byte[]{1, 2}),
+ RequestUtils.getLiteral(new String[]{"a", "b"}))) {
+ byte[] serialized = new TSerializer(new
TCompactProtocol.Factory()).serialize(literal);
+ Literal deserialized = new Literal();
+ new TDeserializer(new
TCompactProtocol.Factory()).deserialize(deserialized, serialized);
+ assertEquals(deserialized, literal);
+ }
+ }
+
+ private static boolean containsBytesArrayLiteral(Expression expression) {
+ if (expression.isSetLiteral()) {
+ return expression.getLiteral().isSetBytesArrayValue();
+ } else if (expression.isSetFunctionCall()) {
+ for (Expression operand : expression.getFunctionCall().getOperands()) {
+ if (containsBytesArrayLiteral(operand)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static boolean containsBytesArrayLiteral(PinotQuery query) {
+ for (Expression expression : query.getSelectList()) {
+ if (containsBytesArrayLiteral(expression)) {
+ return true;
+ }
+ }
+ if (query.isSetFilterExpression() &&
containsBytesArrayLiteral(query.getFilterExpression())) {
+ return true;
+ }
+ if (query.isSetGroupByList()) {
+ for (Expression expression : query.getGroupByList()) {
+ if (containsBytesArrayLiteral(expression)) {
+ return true;
+ }
+ }
+ }
+ if (query.isSetHavingExpression() &&
containsBytesArrayLiteral(query.getHavingExpression())) {
+ return true;
+ }
+ if (query.isSetOrderByList()) {
+ for (Expression expression : query.getOrderByList()) {
+ if (containsBytesArrayLiteral(expression)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git
a/pinot-common/src/test/java/org/apache/pinot/common/request/context/LiteralContextTest.java
b/pinot-common/src/test/java/org/apache/pinot/common/request/context/LiteralContextTest.java
index a89180cf566..7aeb4cd91de 100644
---
a/pinot-common/src/test/java/org/apache/pinot/common/request/context/LiteralContextTest.java
+++
b/pinot-common/src/test/java/org/apache/pinot/common/request/context/LiteralContextTest.java
@@ -19,6 +19,7 @@
package org.apache.pinot.common.request.context;
import java.math.BigDecimal;
+import java.nio.ByteBuffer;
import java.sql.Timestamp;
import java.util.List;
import java.util.UUID;
@@ -28,6 +29,7 @@ import org.apache.pinot.spi.utils.BigDecimalUtils;
import org.apache.pinot.spi.utils.BytesUtils;
import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder;
import org.apache.pinot.spi.utils.UuidUtils;
+import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
@@ -236,6 +238,48 @@ public class LiteralContextTest {
assertEquals(literalContext.toString(), "'deadbeef'");
}
+ @Test
+ public void testBytesArrayLiteral() {
+ Literal literal = Literal.bytesArrayValue(List.of(ByteBuffer.wrap(new
byte[0]), ByteBuffer.wrap(new byte[]{0}),
+ ByteBuffer.wrap(new byte[]{1, 2}), ByteBuffer.wrap(new byte[]{(byte)
0xff})));
+ LiteralContext literalContext = new LiteralContext(literal);
+
+ assertFalse(literalContext.isSingleValue());
+ assertEquals(literalContext.getType(), DataType.BYTES);
+ assertEquals(literalContext.getValue(), new byte[][]{{}, {0}, {1, 2},
{(byte) 0xff}});
+ assertEquals(literalContext.toString(), "'[, 00, 0102, ff]'");
+
+ LiteralContext equalContext =
+ new LiteralContext(DataType.BYTES, new byte[][]{{}, {0}, {1, 2},
{(byte) 0xff}});
+ assertEquals(equalContext, literalContext);
+ assertEquals(equalContext.hashCode(), literalContext.hashCode());
+ assertNotEquals(new LiteralContext(DataType.BYTES, new byte[][]{{}, {0},
{1, 3}, {(byte) 0xff}}), literalContext);
+ }
+
+ @Test(dataProvider = "arrayBackedValues")
+ public void testArrayBackedEqualityAndHashCode(DataType dataType, Object
value, Object equalValue,
+ Object differentValue) {
+ LiteralContext literalContext = new LiteralContext(dataType, value);
+ LiteralContext equalContext = new LiteralContext(dataType, equalValue);
+
+ assertEquals(equalContext, literalContext);
+ assertEquals(equalContext.hashCode(), literalContext.hashCode());
+ assertNotEquals(new LiteralContext(dataType, differentValue),
literalContext);
+ }
+
+ @DataProvider(name = "arrayBackedValues")
+ public Object[][] arrayBackedValues() {
+ return new Object[][]{
+ {DataType.INT, new int[]{1, 2}, new int[]{1, 2}, new int[]{1, 3}},
+ {DataType.LONG, new long[]{1L, 2L}, new long[]{1L, 2L}, new long[]{1L,
3L}},
+ {DataType.FLOAT, new float[]{1.0f, 2.0f}, new float[]{1.0f, 2.0f}, new
float[]{1.0f, 3.0f}},
+ {DataType.DOUBLE, new double[]{1.0, 2.0}, new double[]{1.0, 2.0}, new
double[]{1.0, 3.0}},
+ {DataType.STRING, new String[]{"one", "two"}, new String[]{"one",
"two"}, new String[]{"one", "three"}},
+ {DataType.BYTES, new byte[]{1, 2}, new byte[]{1, 2}, new byte[]{1, 3}},
+ {DataType.BYTES, new byte[][]{{1}, {2}}, new byte[][]{{1}, {2}}, new
byte[][]{{1}, {3}}}
+ };
+ }
+
@Test
public void testUuidLiteral() {
UUID uuid = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
diff --git
a/pinot-common/src/test/java/org/apache/pinot/common/utils/DataSchemaTest.java
b/pinot-common/src/test/java/org/apache/pinot/common/utils/DataSchemaTest.java
index ba3bdceef4e..28fdaa09d80 100644
---
a/pinot-common/src/test/java/org/apache/pinot/common/utils/DataSchemaTest.java
+++
b/pinot-common/src/test/java/org/apache/pinot/common/utils/DataSchemaTest.java
@@ -232,6 +232,23 @@ public class DataSchemaTest {
Assert.assertEquals(BYTES.format(bytesValue),
BytesUtils.toHexString(bytesValue));
}
+ @Test
+ public void testObjectToInternalBytesArray() {
+ Assert.assertEquals((ByteArray[]) OBJECT.toInternal(new byte[0][]), new
ByteArray[0]);
+
+ byte[][] externalBytesArray = {{}, {0}, {1, 2, (byte) 0xFF}};
+ ByteArray[] expected = {new ByteArray(externalBytesArray[0]), new
ByteArray(externalBytesArray[1]),
+ new ByteArray(externalBytesArray[2])};
+ Assert.assertEquals((ByteArray[]) OBJECT.toInternal(externalBytesArray),
expected);
+ }
+
+ @Test
+ public void testObjectToInternalUuidArray() {
+ java.util.UUID[] externalUuidArray = {JAVA_UUID, JAVA_UUID_2};
+ ByteArray[] expected = {new ByteArray(UuidUtils.toBytes(JAVA_UUID)), new
ByteArray(UuidUtils.toBytes(JAVA_UUID_2))};
+ Assert.assertEquals((ByteArray[]) OBJECT.toInternal(externalUuidArray),
expected);
+ }
+
/// The null placeholder must be resolved on the *logical* type. UUID is the
only type whose placeholder differs
/// from its stored type's: it needs the 16-byte nil UUID, while BYTES
supplies a zero-length one. Every other
/// logical type must agree with its stored type, otherwise callers that
resolve the stored type (DataBlockBuilder,
diff --git
a/pinot-common/src/test/java/org/apache/pinot/common/utils/request/RequestUtilsTest.java
b/pinot-common/src/test/java/org/apache/pinot/common/utils/request/RequestUtilsTest.java
index c489740fb07..e9acb5336de 100644
---
a/pinot-common/src/test/java/org/apache/pinot/common/utils/request/RequestUtilsTest.java
+++
b/pinot-common/src/test/java/org/apache/pinot/common/utils/request/RequestUtilsTest.java
@@ -30,6 +30,7 @@ import org.apache.pinot.common.request.ExpressionType;
import org.apache.pinot.common.request.Function;
import org.apache.pinot.common.request.Identifier;
import org.apache.pinot.common.request.Literal;
+import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
import org.apache.pinot.spi.utils.UuidUtils;
import org.apache.pinot.sql.parsers.CalciteSqlParser;
import org.apache.pinot.sql.parsers.PinotSqlType;
@@ -110,6 +111,27 @@ public class RequestUtilsTest {
assertEquals(expression.getLiteral().getBinaryValue(),
UuidUtils.toBytes(uuid));
}
+ @Test
+ public void testBytesArrayLiteralRepresentations() {
+ byte[][] expected = {{0}, {(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte)
0xef}};
+ Literal nativeLiteral = RequestUtils.getLiteral(expected);
+ assertTrue(nativeLiteral.isSetBytesArrayValue());
+ assertEquals(RequestUtils.getBytesArrayValue(nativeLiteral), expected);
+ assertEquals(RequestUtils.getLiteralTypeAndValue(nativeLiteral).getLeft(),
ColumnDataType.BYTES_ARRAY);
+ assertEquals(RequestUtils.prettyPrint(nativeLiteral), "[X'00',
X'deadbeef']");
+
+ Expression expression = CalciteSqlParser.compileToPinotQuery(
+ "SELECT ARRAY[X'00', X'DEADBEEF'] FROM
myTable").getSelectList().get(0);
+ assertTrue(expression.isSetLiteral());
+ assertTrue(expression.getLiteral().isSetBytesArrayValue());
+ assertEquals(RequestUtils.getBytesArrayValue(expression.getLiteral()),
expected);
+
+ expression = CalciteSqlParser.compileToPinotQuery(
+ "SELECT ARRAYS_OVERLAP(ARRAY[X'00', X'0102'], ARRAY[X'03',
X'0102'])").getSelectList().get(0);
+ assertTrue(expression.isSetLiteral());
+ assertTrue(expression.getLiteral().getBoolValue());
+ }
+
@Test
public void testParseQuery() {
SqlNodeAndOptions result = RequestUtils.parseQuery("select foo from
countries where bar > 1");
diff --git a/pinot-common/src/thrift/query.thrift
b/pinot-common/src/thrift/query.thrift
index 728031ca695..92f919c512a 100644
--- a/pinot-common/src/thrift/query.thrift
+++ b/pinot-common/src/thrift/query.thrift
@@ -96,6 +96,7 @@ union Literal {
14: optional list<i32> floatArrayValue;
15: optional list<double> doubleArrayValue;
16: optional list<string> stringArrayValue;
+ 17: optional list<binary> bytesArrayValue;
}
struct Identifier {
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunction.java
index 1919d1ee4c1..a2c8ad3544f 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunction.java
@@ -45,6 +45,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
private final float[] _floatArrayLiteral;
private final double[] _doubleArrayLiteral;
private final String[] _stringArrayLiteral;
+ private final byte[][] _bytesArrayLiteral;
// NOTE:
// This class can be shared across multiple threads, and the result arrays
are lazily initialized and cached. They
@@ -55,6 +56,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
private volatile float[][] _floatArrayResult;
private volatile double[][] _doubleArrayResult;
private volatile String[][] _stringArrayResult;
+ private volatile byte[][][] _bytesArrayResult;
public ArrayLiteralTransformFunction(LiteralContext literalContext) {
_dataType = literalContext.getType();
@@ -65,6 +67,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_floatArrayLiteral = new float[0];
_doubleArrayLiteral = new double[0];
_stringArrayLiteral = new String[0];
+ _bytesArrayLiteral = new byte[0][];
return;
}
switch (_dataType) {
@@ -74,6 +77,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_floatArrayLiteral = null;
_doubleArrayLiteral = null;
_stringArrayLiteral = null;
+ _bytesArrayLiteral = null;
break;
case LONG:
_longArrayLiteral = (long[]) value;
@@ -81,6 +85,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_floatArrayLiteral = null;
_doubleArrayLiteral = null;
_stringArrayLiteral = null;
+ _bytesArrayLiteral = null;
break;
case FLOAT:
_floatArrayLiteral = (float[]) value;
@@ -88,6 +93,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_longArrayLiteral = null;
_doubleArrayLiteral = null;
_stringArrayLiteral = null;
+ _bytesArrayLiteral = null;
break;
case DOUBLE:
_doubleArrayLiteral = (double[]) value;
@@ -95,6 +101,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_longArrayLiteral = null;
_floatArrayLiteral = null;
_stringArrayLiteral = null;
+ _bytesArrayLiteral = null;
break;
case STRING:
_stringArrayLiteral = (String[]) value;
@@ -102,6 +109,15 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_longArrayLiteral = null;
_floatArrayLiteral = null;
_doubleArrayLiteral = null;
+ _bytesArrayLiteral = null;
+ break;
+ case BYTES:
+ _bytesArrayLiteral = (byte[][]) value;
+ _intArrayLiteral = null;
+ _longArrayLiteral = null;
+ _floatArrayLiteral = null;
+ _doubleArrayLiteral = null;
+ _stringArrayLiteral = null;
break;
default:
throw new IllegalStateException(
@@ -119,6 +135,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_floatArrayLiteral = new float[0];
_doubleArrayLiteral = new double[0];
_stringArrayLiteral = new String[0];
+ _bytesArrayLiteral = new byte[0][];
return;
}
for (ExpressionContext literalContext : literalContexts) {
@@ -136,6 +153,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_floatArrayLiteral = null;
_doubleArrayLiteral = null;
_stringArrayLiteral = null;
+ _bytesArrayLiteral = null;
break;
case LONG:
_longArrayLiteral = new long[literalContexts.size()];
@@ -146,6 +164,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_floatArrayLiteral = null;
_doubleArrayLiteral = null;
_stringArrayLiteral = null;
+ _bytesArrayLiteral = null;
break;
case FLOAT:
_floatArrayLiteral = new float[literalContexts.size()];
@@ -156,6 +175,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_longArrayLiteral = null;
_doubleArrayLiteral = null;
_stringArrayLiteral = null;
+ _bytesArrayLiteral = null;
break;
case DOUBLE:
_doubleArrayLiteral = new double[literalContexts.size()];
@@ -166,6 +186,7 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_longArrayLiteral = null;
_floatArrayLiteral = null;
_stringArrayLiteral = null;
+ _bytesArrayLiteral = null;
break;
case STRING:
_stringArrayLiteral = new String[literalContexts.size()];
@@ -176,6 +197,18 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
_longArrayLiteral = null;
_floatArrayLiteral = null;
_doubleArrayLiteral = null;
+ _bytesArrayLiteral = null;
+ break;
+ case BYTES:
+ _bytesArrayLiteral = new byte[literalContexts.size()][];
+ for (int i = 0; i < _bytesArrayLiteral.length; i++) {
+ _bytesArrayLiteral[i] =
literalContexts.get(i).getLiteral().getBytesValue();
+ }
+ _intArrayLiteral = null;
+ _longArrayLiteral = null;
+ _floatArrayLiteral = null;
+ _doubleArrayLiteral = null;
+ _stringArrayLiteral = null;
break;
default:
throw new IllegalStateException(
@@ -204,6 +237,10 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
return _stringArrayLiteral;
}
+ public byte[][] getBytesArrayLiteral() {
+ return _bytesArrayLiteral;
+ }
+
@Override
public String getName() {
return FUNCTION_NAME;
@@ -485,7 +522,14 @@ public class ArrayLiteralTransformFunction implements
TransformFunction {
@Override
public byte[][][] transformToBytesValuesMV(ValueBlock valueBlock) {
- throw new UnsupportedOperationException();
+ int numDocs = valueBlock.getNumDocs();
+ byte[][][] bytesArrayResult = _bytesArrayResult;
+ if (bytesArrayResult == null || bytesArrayResult.length < numDocs) {
+ bytesArrayResult = new byte[numDocs][][];
+ Arrays.fill(bytesArrayResult, _bytesArrayLiteral);
+ _bytesArrayResult = bytesArrayResult;
+ }
+ return bytesArrayResult;
}
@Override
diff --git
a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunctionTest.java
b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunctionTest.java
index 2bbdab74e0d..bac0e366177 100644
---
a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunctionTest.java
+++
b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunctionTest.java
@@ -22,8 +22,11 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.pinot.common.request.Literal;
import org.apache.pinot.common.request.context.ExpressionContext;
+import org.apache.pinot.common.request.context.LiteralContext;
+import org.apache.pinot.common.utils.request.RequestUtils;
import org.apache.pinot.core.operator.blocks.ProjectionBlock;
import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.apache.pinot.spi.utils.BytesUtils;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.Assert;
@@ -123,6 +126,28 @@ public class ArrayLiteralTransformFunctionTest {
});
}
+ @Test
+ public void testBytesArrayLiteralTransformFunction() {
+ byte[][] expected = {BytesUtils.toBytes("00"),
BytesUtils.toBytes("deadbeef")};
+ List<ExpressionContext> arrayExpressions = List.of(
+ ExpressionContext.forLiteral(DataType.BYTES, expected[0]),
+ ExpressionContext.forLiteral(DataType.BYTES, expected[1]));
+
+ List<ArrayLiteralTransformFunction> bytesArrays = List.of(new
ArrayLiteralTransformFunction(arrayExpressions),
+ new ArrayLiteralTransformFunction(new
LiteralContext(RequestUtils.getLiteral(expected))));
+ for (ArrayLiteralTransformFunction bytesArray : bytesArrays) {
+ Assert.assertEquals(bytesArray.getResultMetadata().getDataType(),
DataType.BYTES);
+ Assert.assertFalse(bytesArray.getResultMetadata().isSingleValue());
+ Assert.assertEquals(bytesArray.getBytesArrayLiteral(), expected);
+
+ byte[][][] values =
bytesArray.transformToBytesValuesMV(_projectionBlock);
+ Assert.assertEquals(values.length, NUM_DOCS);
+ for (byte[][] value : values) {
+ Assert.assertEquals(value, expected);
+ }
+ }
+ }
+
@Test
public void testEmptyArrayTransform() {
List<ExpressionContext> arrayExpressions = new ArrayList<>();
diff --git
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/custom/BytesMvTypeTest.java
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/custom/BytesMvTypeTest.java
index 5e7f2c8c337..25b440b931f 100644
---
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/custom/BytesMvTypeTest.java
+++
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/custom/BytesMvTypeTest.java
@@ -35,6 +35,7 @@ import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
@@ -183,6 +184,66 @@ public class BytesMvTypeTest extends
CustomDataQueryClusterIntegrationTest {
}
}
+ @Test(dataProvider = "useBothQueryEngines")
+ public void testBytesArrayLiteral(boolean useMultiStageQueryEngine)
+ throws Exception {
+ setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+ String arrayLiteral = "ARRAY[X'07', X'0708', X'07090A']";
+ for (boolean withFrom : new boolean[]{true, false}) {
+ String query = withFrom ? String.format("SELECT %s FROM %s WHERE %s = 7
LIMIT 1", arrayLiteral,
+ getTableName(), ID_COLUMN) : "SELECT " + arrayLiteral;
+ JsonNode result = postQuery(query).get("resultTable");
+
assertEquals(result.get("dataSchema").get("columnDataTypes").get(0).asText(),
"BYTES_ARRAY");
+ JsonNode values = result.get("rows").get(0).get(0);
+ assertEquals(values.size(), MV_LENGTH);
+ assertEquals(values.get(0).asText(), "07");
+ assertEquals(values.get(1).asText(), "0708");
+ assertEquals(values.get(2).asText(), "07090a");
+ }
+ }
+
+ @Test(dataProvider = "useBothQueryEngines")
+ public void testBytesArrayLiteralRejectsUnsupportedElements(boolean
useMultiStageQueryEngine)
+ throws Exception {
+ setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+ for (String expression : List.of("ARRAY[X'00', NULL]", "ARRAY[NULL,
X'00']", "ARRAY[X'00', 1]",
+ "ARRAY[1, X'00']")) {
+ JsonNode response = postQuery("SELECT " + expression);
+ assertFalse(response.path("exceptions").isEmpty(),
+ "Expected unsupported BYTES array elements to be rejected: " +
response.toPrettyString());
+ }
+ }
+
+ @Test(dataProvider = "useBothQueryEngines")
+ public void testArraysOverlapWithLiteral(boolean useMultiStageQueryEngine)
+ throws Exception {
+ setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+ for (String mvCol : MV_COLUMNS) {
+ String positiveQuery = String.format(
+ "SELECT COUNT(*) FROM %s WHERE ARRAYS_OVERLAP(%s, ARRAY[X'07'])",
getTableName(), mvCol);
+ JsonNode rows = postQuery(positiveQuery).get("resultTable").get("rows");
+ assertEquals(rows.get(0).get(0).asLong(), 1L);
+
+ String negativeQuery = String.format(
+ "SELECT COUNT(*) FROM %s WHERE ARRAYS_OVERLAP(%s, ARRAY[X'FF'])",
getTableName(), mvCol);
+ rows = postQuery(negativeQuery).get("resultTable").get("rows");
+ assertEquals(rows.get(0).get(0).asLong(), 0L);
+ }
+ }
+
+ @Test(dataProvider = "useBothQueryEngines")
+ public void testArraysOverlapWithLiterals(boolean useMultiStageQueryEngine)
+ throws Exception {
+ setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+ JsonNode result = postQuery(
+ "SELECT ARRAYS_OVERLAP(ARRAY[X'00', X'0102'], ARRAY[X'03',
X'0102'])").get("resultTable");
+ assertTrue(result.get("rows").get(0).get(0).asBoolean());
+
+ result = postQuery(
+ "SELECT ARRAYS_OVERLAP(ARRAY[X'00', X'0102'], ARRAY[X'03',
X'04'])").get("resultTable");
+ assertFalse(result.get("rows").get(0).get(0).asBoolean());
+ }
+
@Test(dataProvider = "useBothQueryEngines")
public void testCountStar(boolean useMultiStageQueryEngine)
throws Exception {
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/query/parser/CalciteRexExpressionParser.java
b/pinot-query-planner/src/main/java/org/apache/pinot/query/parser/CalciteRexExpressionParser.java
index d6f270d6f56..e4b7a3f35db 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/query/parser/CalciteRexExpressionParser.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/query/parser/CalciteRexExpressionParser.java
@@ -139,13 +139,20 @@ public class CalciteRexExpressionParser {
return RequestUtils.getNullLiteral();
}
// NOTE: Value is stored in internal format in RexExpression.Literal.
- // Do not convert TIMESTAMP/BOOLEAN_ARRAY/TIMESTAMP_ARRAY to
external format because they are not explicitly
- // supported in single-stage engine Literal.
+ // Do not convert TIMESTAMP/BOOLEAN_ARRAY/TIMESTAMP_ARRAY/UUID_ARRAY
to external format because they are not
+ // explicitly supported in single-stage engine Literal.
ColumnDataType dataType = literal.getDataType();
if (dataType == ColumnDataType.BOOLEAN) {
value = BooleanUtils.isTrueInternalValue(value);
} else if (dataType == ColumnDataType.BYTES || dataType ==
ColumnDataType.UUID) {
value = ((ByteArray) value).getBytes();
+ } else if (dataType == ColumnDataType.BYTES_ARRAY) {
+ ByteArray[] byteArrays = (ByteArray[]) value;
+ byte[][] bytes = new byte[byteArrays.length][];
+ for (int i = 0; i < byteArrays.length; i++) {
+ bytes[i] = byteArrays[i].getBytes();
+ }
+ value = bytes;
}
return RequestUtils.getLiteral(value);
}
diff --git
a/pinot-query-planner/src/test/java/org/apache/pinot/query/parser/CalciteRexExpressionParserTest.java
b/pinot-query-planner/src/test/java/org/apache/pinot/query/parser/CalciteRexExpressionParserTest.java
index c604a8dce59..08aca0c1d3f 100644
---
a/pinot-query-planner/src/test/java/org/apache/pinot/query/parser/CalciteRexExpressionParserTest.java
+++
b/pinot-query-planner/src/test/java/org/apache/pinot/query/parser/CalciteRexExpressionParserTest.java
@@ -20,6 +20,7 @@ package org.apache.pinot.query.parser;
import org.apache.pinot.common.request.Literal;
import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
+import org.apache.pinot.common.utils.request.RequestUtils;
import org.apache.pinot.query.planner.logical.RexExpression;
import org.apache.pinot.spi.utils.ByteArray;
import org.apache.pinot.spi.utils.UuidUtils;
@@ -43,4 +44,16 @@ public class CalciteRexExpressionParserTest {
assertTrue(literal.isSetBinaryValue());
assertEquals(literal.getBinaryValue(), UuidUtils.toBytes(UUID_VALUE));
}
+
+ @Test
+ public void testBytesArrayLiteralUsesBytesArrayValue() {
+ byte[][] expected = {{0}, {1, 2}};
+ ByteArray[] internalValue = {new ByteArray(expected[0]), new
ByteArray(expected[1])};
+ RexExpression.Literal bytesArrayLiteral = new
RexExpression.Literal(ColumnDataType.BYTES_ARRAY, internalValue);
+
+ Literal literal = CalciteRexExpressionParser.toLiteral(bytesArrayLiteral);
+
+ assertTrue(literal.isSetBytesArrayValue());
+ assertEquals(RequestUtils.getBytesArrayValue(literal), expected);
+ }
}
diff --git
a/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/serde/RexExpressionSerDeTest.java
b/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/serde/RexExpressionSerDeTest.java
index ffd8a62e76c..d09fcd8d02e 100644
---
a/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/serde/RexExpressionSerDeTest.java
+++
b/pinot-query-planner/src/test/java/org/apache/pinot/query/planner/serde/RexExpressionSerDeTest.java
@@ -38,7 +38,8 @@ public class RexExpressionSerDeTest {
ColumnDataType.BIG_DECIMAL, ColumnDataType.BOOLEAN,
ColumnDataType.TIMESTAMP, ColumnDataType.STRING,
ColumnDataType.BYTES, ColumnDataType.UUID, ColumnDataType.INT_ARRAY,
ColumnDataType.LONG_ARRAY,
ColumnDataType.FLOAT_ARRAY, ColumnDataType.DOUBLE_ARRAY,
ColumnDataType.BOOLEAN_ARRAY,
- ColumnDataType.TIMESTAMP_ARRAY, ColumnDataType.STRING_ARRAY,
ColumnDataType.UUID_ARRAY,
+ ColumnDataType.TIMESTAMP_ARRAY, ColumnDataType.STRING_ARRAY,
ColumnDataType.BYTES_ARRAY,
+ ColumnDataType.UUID_ARRAY,
ColumnDataType.UNKNOWN);
private static final Random RANDOM = new Random();
@@ -167,6 +168,12 @@ public class RexExpressionSerDeTest {
verifyLiteralSerDe(new RexExpression.Literal(ColumnDataType.STRING_ARRAY,
values));
}
+ @Test
+ public void testBytesArrayLiteral() {
+ ByteArray[] values = {new ByteArray(new byte[0]), new ByteArray(new
byte[]{1, 2, 3})};
+ verifyLiteralSerDe(new RexExpression.Literal(ColumnDataType.BYTES_ARRAY,
values));
+ }
+
@Test
public void testUuidArrayLiteral() {
ByteArray[] values = {
diff --git
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/TransformOperatorTest.java
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/TransformOperatorTest.java
index ca33f5c1fd1..71759056b4a 100644
---
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/TransformOperatorTest.java
+++
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/TransformOperatorTest.java
@@ -114,6 +114,58 @@ public class TransformOperatorTest {
assertEquals(resultRows.get(1), new Object[]{5.0, -1.0});
}
+ @Test
+ public void shouldHandleBytesArrayLiteralTransform() {
+ DataSchema inputSchema = new DataSchema(new String[]{"intCol"}, new
ColumnDataType[]{ColumnDataType.INT});
+ when(_input.nextBlock()).thenReturn(
+ OperatorTestUtil.block(inputSchema, new Object[]{1}, new Object[]{2},
new Object[]{3}));
+ DataSchema resultSchema =
+ new DataSchema(new String[]{"bytesArray"}, new
ColumnDataType[]{ColumnDataType.BYTES_ARRAY});
+ ByteArray first = new ByteArray(new byte[]{0});
+ ByteArray second = new ByteArray(new byte[]{1, 2});
+ List<RexExpression> operands = List.of(new
RexExpression.Literal(ColumnDataType.BYTES, first),
+ new RexExpression.Literal(ColumnDataType.BYTES, second));
+ List<RexExpression> projects = List.of(
+ new RexExpression.FunctionCall(ColumnDataType.BYTES_ARRAY,
"ARRAY_VALUE_CONSTRUCTOR", operands));
+
+ TransformOperator operator = getOperator(inputSchema, resultSchema,
projects);
+ List<Object[]> resultRows = ((MseBlock.Data)
operator.nextBlock()).asRowHeap().getRows();
+ assertEquals(resultRows.size(), 3);
+ for (Object[] resultRow : resultRows) {
+ assertEquals((ByteArray[]) resultRow[0], new ByteArray[]{first, second});
+ }
+ }
+
+ @Test
+ public void shouldHandleDynamicBytesArrayTransform() {
+ DataSchema inputSchema =
+ new DataSchema(new String[]{"left", "right"}, new
ColumnDataType[]{ColumnDataType.BYTES, ColumnDataType.BYTES});
+ ByteArray literal = new ByteArray(new byte[]{0});
+ ByteArray left0 = new ByteArray(new byte[]{1});
+ ByteArray right0 = new ByteArray(new byte[]{2});
+ ByteArray left1 = new ByteArray(new byte[]{3});
+ ByteArray right1 = new ByteArray(new byte[]{4});
+ ByteArray left2 = new ByteArray(new byte[]{5});
+ ByteArray right2 = new ByteArray(new byte[]{6});
+ when(_input.nextBlock()).thenReturn(OperatorTestUtil.block(inputSchema,
new Object[]{left0, right0},
+ new Object[]{left1, right1}, new Object[]{left2, right2}));
+ DataSchema resultSchema =
+ new DataSchema(new String[]{"bytesArray"}, new
ColumnDataType[]{ColumnDataType.BYTES_ARRAY});
+ List<RexExpression> operands = List.of(new
RexExpression.Literal(ColumnDataType.BYTES, literal),
+ new RexExpression.InputRef(0), new RexExpression.InputRef(1));
+ List<RexExpression> projects = List.of(
+ new RexExpression.FunctionCall(ColumnDataType.BYTES_ARRAY,
"ARRAY_VALUE_CONSTRUCTOR", operands));
+
+ TransformOperator operator = getOperator(inputSchema, resultSchema,
projects);
+ List<Object[]> resultRows = ((MseBlock.Data)
operator.nextBlock()).asRowHeap().getRows();
+ assertEquals(resultRows.size(), 3);
+ ByteArray[][] expected = {{literal, left0, right0}, {literal, left1,
right1}, {literal, left2, right2}};
+ for (int i = 0; i < expected.length; i++) {
+ ByteArray[] actual = (ByteArray[]) resultRows.get(i)[0];
+ assertEquals(actual, expected[i]);
+ }
+ }
+
@Test
public void shouldRenderUuidToStringAsCanonicalText() {
String uuid = "550e8400-e29b-41d4-a716-446655440000";
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]