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 2050fce9bd0 [UUID 3/8] UUID result rendering (DataSchema, Arrow/JSON 
encoders) (#18871)
2050fce9bd0 is described below

commit 2050fce9bd0dd2c4b2e26df83ecf89dfb3f7f844
Author: Xiang Fu <[email protected]>
AuthorDate: Sun Jul 26 13:28:59 2026 -0700

    [UUID 3/8] UUID result rendering (DataSchema, Arrow/JSON encoders) (#18871)
    
    Part 3/8 of splitting apache/pinot#18140 (logical UUID type). Rebased onto
    master, which now includes the merged #18869 type foundation and #18870
    ingest/storage layer.
    
    Renders UUID result columns as canonical lowercase RFC-4122 strings:
    - DataSchema.ColumnDataType.UUID / UUID_ARRAY plus the conversion and format
      helpers, backed by the UuidUtils class merged in #18869.
    - Arrow and JSON broker response encoders.
    
    UUID null placeholder: ColumnDataType.UUID overrides getNullPlaceholder() to
    return the nil UUID, matching the default null sentinel FieldSpec already 
uses
    for UUID columns. Its stored type BYTES supplies a zero-length placeholder,
    which is not a valid 16-byte UUID and fails to render, so the three call 
sites
    that build null-aware blocks (DataBlockBuilder, GroupByResultsBlock,
    GroupByDataTableReducer) now resolve the placeholder on the logical type 
rather
    than the stored type. For every other logical type the two are identical, an
    invariant pinned by a new DataSchemaTest case.
    
    DataSchema.fromBytes now reports an unrecognized ColumnDataType token with a
    message naming the mixed-version cause instead of a bare 
IllegalArgumentException.
    
    Rolling-upgrade note: DataSchema serializes ColumnDataType by name, so an 
old
    broker/server that receives a UUID token from a new node cannot parse it. 
Treat
    as an atomic-upgrade feature - UUID columns should only be queried once the
    whole cluster is upgraded. No existing (non-UUID) column is affected.
---
 .../response/encoder/ArrowResponseEncoder.java     |   6 +
 .../response/encoder/JsonResponseEncoder.java      |   2 +
 .../org/apache/pinot/common/utils/DataSchema.java  | 172 ++++++++++++++++++++-
 .../response/encoder/ArrowResponseEncoderTest.java |  79 +++++++++-
 .../response/encoder/JsonResponseEncoderTest.java  |  52 ++++++-
 .../apache/pinot/common/utils/DataSchemaTest.java  |  88 ++++++++++-
 .../core/common/datablock/DataBlockBuilder.java    |  40 +++--
 .../blocks/results/GroupByResultsBlock.java        |   4 +-
 .../core/query/reduce/GroupByDataTableReducer.java |   4 +-
 .../common/datablock/DataBlockBuilderTest.java     |  23 +++
 .../core/common/datablock/DataBlockTestUtils.java  |  13 ++
 .../core/common/datatable/DataTableSerDeTest.java  |  26 ++++
 .../selection/SelectionOperatorUtilsTest.java      |  23 +++
 13 files changed, 497 insertions(+), 35 deletions(-)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/response/encoder/ArrowResponseEncoder.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/response/encoder/ArrowResponseEncoder.java
index f96027398c5..efd9098991e 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/response/encoder/ArrowResponseEncoder.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/response/encoder/ArrowResponseEncoder.java
@@ -107,6 +107,7 @@ public class ArrowResponseEncoder implements 
ResponseEncoder {
         case STRING:
         case JSON:
         case BYTES:
+        case UUID:
         case OBJECT:
           field = new Field(colName, FieldType.nullable(new ArrowType.Utf8()), 
null);
           vector = new VarCharVector(colName, ALLOCATOR);
@@ -181,6 +182,7 @@ public class ArrowResponseEncoder implements 
ResponseEncoder {
         case TIMESTAMP_ARRAY:
         case STRING_ARRAY:
         case BYTES_ARRAY:
+        case UUID_ARRAY:
           // Define the inner field for a string element.
           children = List.of(new Field("element", FieldType.nullable(new 
ArrowType.Utf8()), null));
           // Define the field for the list column.
@@ -234,6 +236,7 @@ public class ArrowResponseEncoder implements 
ResponseEncoder {
             case STRING:
             case JSON:
             case BYTES:
+            case UUID:
             case OBJECT:
               byte[] bytes = ((String) value).getBytes(StandardCharsets.UTF_8);
               ((VarCharVector) vector).setSafe(rowIndex, bytes);
@@ -345,6 +348,7 @@ public class ArrowResponseEncoder implements 
ResponseEncoder {
             case TIMESTAMP_ARRAY:
             case STRING_ARRAY:
             case BYTES_ARRAY:
+            case UUID_ARRAY:
               ListVector listVector = (ListVector) vector;
               String[] stringArray = (String[]) value;
               // Start a new list entry for the current row.
@@ -411,6 +415,7 @@ public class ArrowResponseEncoder implements 
ResponseEncoder {
             case STRING:
             case JSON:
             case BYTES:
+            case UUID:
             case OBJECT:
               row[col] = new String(((VarCharVector) vector).get(i), 
StandardCharsets.UTF_8);
               break;
@@ -469,6 +474,7 @@ public class ArrowResponseEncoder implements 
ResponseEncoder {
             case TIMESTAMP_ARRAY:
             case STRING_ARRAY:
             case BYTES_ARRAY:
+            case UUID_ARRAY:
               ListVector listVector = (ListVector) vector;
               List<?> arrayValues = listVector.getObject(i);
               String[] array = new String[arrayValues.size()];
diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/response/encoder/JsonResponseEncoder.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/response/encoder/JsonResponseEncoder.java
index 0f82daab9e9..aa86400de85 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/response/encoder/JsonResponseEncoder.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/response/encoder/JsonResponseEncoder.java
@@ -194,6 +194,7 @@ public class JsonResponseEncoder implements ResponseEncoder 
{
       case TIMESTAMP_ARRAY:
       case STRING_ARRAY:
       case BYTES_ARRAY:
+      case UUID_ARRAY:
         String[] stringArray = new String[jsonValue.size()];
         for (int k = 0; k < jsonValue.size(); k++) {
           stringArray[k] = jsonValue.get(k).textValue();
@@ -224,6 +225,7 @@ public class JsonResponseEncoder implements ResponseEncoder 
{
       case STRING:
       case JSON:
       case BYTES:
+      case UUID:
       case OBJECT:
         return jsonValue.textValue();
       case UNKNOWN:
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 5149195d68c..978b33aed80 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
@@ -40,6 +40,7 @@ import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.sql.type.SqlTypeName;
@@ -51,6 +52,7 @@ import org.apache.pinot.spi.utils.BytesUtils;
 import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder;
 import org.apache.pinot.spi.utils.EqualityUtils;
 import org.apache.pinot.spi.utils.PinotDataType;
+import org.apache.pinot.spi.utils.UuidUtils;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
@@ -135,7 +137,7 @@ public class DataSchema {
     // Write the column types.
     for (ColumnDataType columnDataType : _columnDataTypes) {
       // We don't want to use ordinal of the enum since adding a new data type 
will break things if server and broker
-      // use different versions of DataType class.
+      // use different versions of DataType class. See parseColumnDataType() 
for the mixed-version read side.
       byte[] bytes = columnDataType.name().getBytes(UTF_8);
       dataOutputStream.writeInt(bytes.length);
       dataOutputStream.write(bytes);
@@ -164,7 +166,7 @@ public class DataSchema {
       int length = buffer.getInt();
       byte[] bytes = new byte[length];
       buffer.get(bytes);
-      columnDataTypes[i] = ColumnDataType.valueOf(new String(bytes, UTF_8));
+      columnDataTypes[i] = parseColumnDataType(new String(bytes, UTF_8));
     }
     return new DataSchema(columnNames, columnDataTypes);
   }
@@ -187,11 +189,31 @@ public class DataSchema {
       int length = buffer.readInt();
       byte[] bytes = new byte[length];
       buffer.readFully(bytes);
-      columnDataTypes[i] = ColumnDataType.valueOf(new String(bytes, UTF_8));
+      columnDataTypes[i] = parseColumnDataType(new String(bytes, UTF_8));
     }
     return new DataSchema(columnNames, columnDataTypes);
   }
 
+  /// Resolves a [ColumnDataType] token read off the wire, turning the raw 
[IllegalArgumentException] from
+  /// [ColumnDataType#valueOf] into a message that names the mixed-version 
cause.
+  ///
+  /// Rolling-upgrade limitation: once a node on this build emits a `UUID` 
token, an older peer that does not know
+  /// the [ColumnDataType#UUID] constant fails here. There is no 
version-negotiation shim or fallback to `BYTES`
+  /// today, so brokers and servers must be upgraded atomically (or UUID 
columns kept out of queries) until the
+  /// whole cluster is on this build. Rolling back to a pre-UUID build is 
likewise unsafe while UUID-typed query
+  /// results are in flight. No existing (non-UUID) column is affected.
+  private static ColumnDataType parseColumnDataType(String name) {
+    try {
+      return ColumnDataType.valueOf(name);
+    } catch (IllegalArgumentException e) {
+      throw new IllegalArgumentException(
+          "Unrecognized ColumnDataType '" + name + "' received from a peer 
node. This typically means the peer is "
+              + "running a newer build that introduced a data type not yet 
known to this node. Upgrade all brokers "
+              + "and servers to the same build before querying columns of that 
type, or keep those columns out of "
+              + "queries until the rolling upgrade is complete.", e);
+    }
+  }
+
   @SuppressWarnings("MethodDoesntCallSuperMethod")
   @Override
   public DataSchema clone() {
@@ -297,6 +319,25 @@ public class DataSchema {
         return typeFactory.createSqlType(SqlTypeName.VARBINARY);
       }
     },
+    // UUID is a logical type backed by BYTES; keep it directly after BYTES. 
ColumnDataType is serialized by name (not
+    // ordinal) via name()/valueOf(), so enum order does not affect wire 
compatibility.
+    UUID(BYTES, null) {
+      @Override
+      public RelDataType toType(RelDataTypeFactory typeFactory) {
+        return typeFactory.createSqlType(SqlTypeName.UUID);
+      }
+
+      /// Returns the nil UUID, matching the default null sentinel that 
[FieldSpec#getDefaultNullValue] uses for
+      /// UUID columns. This is the one type whose placeholder differs from 
its stored type's: `BYTES` uses a shared
+      /// zero-length [ByteArray], which is not a valid 16-byte UUID and would 
fail to render.
+      ///
+      /// A fresh instance is returned per call because, unlike every other 
placeholder (all empty or immutable),
+      /// this one wraps a mutable 16-byte array that callers hand out as a 
column fill value.
+      @Override
+      public Object getNullPlaceholder() {
+        return new ByteArray(UuidUtils.nullUuidBytes());
+      }
+    },
     MAP(NullValuePlaceHolder.MAP) {
       @Override
       public RelDataType toType(RelDataTypeFactory typeFactory) {
@@ -363,6 +404,12 @@ public class DataSchema {
         return typeFactory.createArrayType(BYTES.toType(typeFactory), -1);
       }
     },
+    UUID_ARRAY(BYTES_ARRAY, NullValuePlaceHolder.INTERNAL_BYTES_ARRAY) {
+      @Override
+      public RelDataType toType(RelDataTypeFactory typeFactory) {
+        return typeFactory.createArrayType(UUID.toType(typeFactory), -1);
+      }
+    },
     UNKNOWN(null) {
       @Override
       public RelDataType toType(RelDataTypeFactory typeFactory) {
@@ -374,7 +421,7 @@ public class DataSchema {
     private static final EnumSet<ColumnDataType> INTEGRAL_TYPES = 
EnumSet.of(INT, LONG);
     private static final EnumSet<ColumnDataType> ARRAY_TYPES =
         EnumSet.of(INT_ARRAY, LONG_ARRAY, FLOAT_ARRAY, DOUBLE_ARRAY, 
BIG_DECIMAL_ARRAY, BOOLEAN_ARRAY, TIMESTAMP_ARRAY,
-            STRING_ARRAY, BYTES_ARRAY);
+            STRING_ARRAY, BYTES_ARRAY, UUID_ARRAY);
     private static final EnumSet<ColumnDataType> NUMERIC_ARRAY_TYPES =
         EnumSet.of(INT_ARRAY, LONG_ARRAY, FLOAT_ARRAY, DOUBLE_ARRAY, 
BIG_DECIMAL_ARRAY);
     private static final EnumSet<ColumnDataType> INTEGRAL_ARRAY_TYPES = 
EnumSet.of(INT_ARRAY, LONG_ARRAY);
@@ -395,6 +442,10 @@ public class DataSchema {
       _nullPlaceholder = nullPlaceHolder;
     }
 
+    /// Returns the value used to fill null entries in the serialized column, 
masked on read by the null bitmap.
+    ///
+    /// Callers must resolve this on the *logical* type, not on 
[#getStoredType], because [#UUID] overrides it (see
+    /// that constant). For every other logical type the two agree, an 
invariant pinned by `DataSchemaTest`.
     public Object getNullPlaceholder() {
       return _nullPlaceholder;
     }
@@ -463,6 +514,9 @@ public class DataSchema {
         case BYTES:
         case BYTES_ARRAY:
           return DataType.BYTES;
+        case UUID:
+        case UUID_ARRAY:
+          return DataType.UUID;
         case UNKNOWN:
           return DataType.UNKNOWN;
         default:
@@ -490,6 +544,8 @@ public class DataSchema {
      *   <li>BYTES: byte[] -> ByteArray</li>
      *   <li>BOOLEAN_ARRAY: boolean[] -> int[]</li>
      *   <li>TIMESTAMP_ARRAY: Timestamp[] -> long[]</li>
+     *   <li>UUID: UUID/String/byte[]/ByteArray -> ByteArray</li>
+     *   <li>UUID_ARRAY: UUID[]/String[]/byte[][]/ByteArray[] -> 
ByteArray[]</li>
      * </ul>
      */
     public Object toInternal(Object value) {
@@ -500,10 +556,14 @@ public class DataSchema {
           return ((Timestamp) value).getTime();
         case BYTES:
           return new ByteArray((byte[]) value);
+        case UUID:
+          return new ByteArray(UuidUtils.toBytes(value));
         case BOOLEAN_ARRAY:
           return fromBooleanArray((boolean[]) value);
         case TIMESTAMP_ARRAY:
           return fromTimestampArray((Timestamp[]) value);
+        case UUID_ARRAY:
+          return fromUuidArray(value);
         case OBJECT:
           // For OBJECT type, we need to convert based on the actual type of 
the value. This can happen when the scalar
           // function returns Object type, e.g. cast function.
@@ -516,6 +576,9 @@ public class DataSchema {
           if (value instanceof byte[]) {
             return new ByteArray((byte[]) value);
           }
+          if (value instanceof UUID) {
+            return new ByteArray(UuidUtils.toBytes((UUID) value));
+          }
           if (value instanceof boolean[]) {
             return fromBooleanArray((boolean[]) value);
           }
@@ -539,6 +602,8 @@ public class DataSchema {
      *   <li>BOOLEAN_ARRAY: int[] -> boolean[]</li>
      *   <li>TIMESTAMP_ARRAY: long[] -> Timestamp[]</li>
      *   <li>BYTES_ARRAY: ByteArray[] -> byte[][]</li>
+     *   <li>UUID: ByteArray -> UUID</li>
+     *   <li>UUID_ARRAY: ByteArray[] -> UUID[]</li>
      * </ul>
      */
     public Object toExternal(Object value) {
@@ -549,12 +614,16 @@ public class DataSchema {
           return new Timestamp((long) value);
         case BYTES:
           return ((ByteArray) value).getBytes();
+        case UUID:
+          return UuidUtils.toUUID((ByteArray) value);
         case BOOLEAN_ARRAY:
           return toBooleanArray((int[]) value);
         case TIMESTAMP_ARRAY:
           return toTimestampArray((long[]) value);
         case BYTES_ARRAY:
           return toBytesArray(value);
+        case UUID_ARRAY:
+          return toUuidArray(value);
         default:
           return value;
       }
@@ -585,6 +654,8 @@ public class DataSchema {
           return value.toString();
         case BYTES:
           return ((ByteArray) value).getBytes();
+        case UUID:
+          return UuidUtils.toUUID((ByteArray) value);
         case INT_ARRAY:
           return toIntArray(value);
         case LONG_ARRAY:
@@ -603,6 +674,8 @@ public class DataSchema {
           return toStringArray(value);
         case BYTES_ARRAY:
           return toBytesArray(value);
+        case UUID_ARRAY:
+          return toUuidArray(value);
         case UNKNOWN: // fall through
         case OBJECT:
           return (Serializable) value;
@@ -624,12 +697,16 @@ public class DataSchema {
           return value.toString();
         case BYTES:
           return BytesUtils.toHexString((byte[]) value);
+        case UUID:
+          return formatUuid(value);
         case BIG_DECIMAL_ARRAY:
           return formatBigDecimalArray((BigDecimal[]) value);
         case TIMESTAMP_ARRAY:
           return formatTimestampArray((Timestamp[]) value);
         case BYTES_ARRAY:
           return formatBytesArray((byte[][]) value);
+        case UUID_ARRAY:
+          return formatUuidArray(value);
         default:
           return (Serializable) value;
       }
@@ -659,6 +736,8 @@ public class DataSchema {
           return value.toString();
         case BYTES:
           return ((ByteArray) value).toHexString();
+        case UUID:
+          return UuidUtils.toString((ByteArray) value);
         case MAP:
           return toMap(value);
         case INT_ARRAY:
@@ -679,6 +758,8 @@ public class DataSchema {
           return (String[]) value;
         case BYTES_ARRAY:
           return formatBytesArray((ByteArray[]) value);
+        case UUID_ARRAY:
+          return formatUuidArray(value);
         default:
           throw new IllegalStateException(String.format("Cannot convert and 
format: '%s' to type: %s", value, this));
       }
@@ -869,6 +950,54 @@ public class DataSchema {
       throw new IllegalStateException(String.format("Cannot convert: '%s' to 
byte[][]", value));
     }
 
+    /// Converts any supported UUID array representation to `UUID[]`. Elements 
may be `UUID`, `byte[]`, [ByteArray]
+    /// or `CharSequence`; per-element dispatch is delegated to 
[UuidUtils#toUUID(Object)]. Note that `byte[][]`,
+    /// [ByteArray]`[]`, `String[]` and `UUID[]` are all `Object[]`, so one 
branch covers every array form.
+    private static UUID[] toUuidArray(Object value) {
+      if (value instanceof UUID[]) {
+        return (UUID[]) value;
+      }
+      if (value instanceof ObjectArrayList) {
+        ObjectArrayList<?> list = (ObjectArrayList<?>) value;
+        int size = list.size();
+        UUID[] uuidArray = new UUID[size];
+        for (int i = 0; i < size; i++) {
+          uuidArray[i] = UuidUtils.toUUID(list.get(i));
+        }
+        return uuidArray;
+      }
+      Object[] valueArray = (Object[]) value;
+      int length = valueArray.length;
+      UUID[] uuidArray = new UUID[length];
+      for (int i = 0; i < length; i++) {
+        uuidArray[i] = UuidUtils.toUUID(valueArray[i]);
+      }
+      return uuidArray;
+    }
+
+    /// Inverse of [#toUuidArray]: converts any supported UUID array 
representation to the internal [ByteArray]`[]`.
+    private static ByteArray[] fromUuidArray(Object value) {
+      if (value instanceof ByteArray[]) {
+        return (ByteArray[]) value;
+      }
+      if (value instanceof ObjectArrayList) {
+        ObjectArrayList<?> list = (ObjectArrayList<?>) value;
+        int size = list.size();
+        ByteArray[] wrapped = new ByteArray[size];
+        for (int i = 0; i < size; i++) {
+          wrapped[i] = new ByteArray(UuidUtils.toBytes(list.get(i)));
+        }
+        return wrapped;
+      }
+      Object[] valueArray = (Object[]) value;
+      int length = valueArray.length;
+      ByteArray[] wrapped = new ByteArray[length];
+      for (int i = 0; i < length; i++) {
+        wrapped[i] = new ByteArray(UuidUtils.toBytes(valueArray[i]));
+      }
+      return wrapped;
+    }
+
     private static String[] formatBytesArray(byte[][] bytesArray) {
       int length = bytesArray.length;
       String[] formattedBytesArray = new String[length];
@@ -887,6 +1016,28 @@ public class DataSchema {
       return formattedBytesArray;
     }
 
+    /// Renders any supported UUID array representation as canonical lowercase 
RFC 4122 strings. Per-element
+    /// dispatch is delegated to [#formatUuid], so `byte[][]`, 
[ByteArray]`[]`, `UUID[]` and `String[]` are all
+    /// handled by the single `Object[]` branch.
+    private static String[] formatUuidArray(Object value) {
+      if (value instanceof ObjectArrayList) {
+        ObjectArrayList<?> list = (ObjectArrayList<?>) value;
+        int size = list.size();
+        String[] formattedUuidArray = new String[size];
+        for (int i = 0; i < size; i++) {
+          formattedUuidArray[i] = formatUuid(list.get(i));
+        }
+        return formattedUuidArray;
+      }
+      Object[] valueArray = (Object[]) value;
+      int length = valueArray.length;
+      String[] formattedUuidArray = new String[length];
+      for (int i = 0; i < length; i++) {
+        formattedUuidArray[i] = formatUuid(valueArray[i]);
+      }
+      return formattedUuidArray;
+    }
+
     public static ColumnDataType fromDataType(DataType dataType, boolean 
isSingleValue) {
       return isSingleValue ? fromDataTypeSV(dataType) : 
fromDataTypeMV(dataType);
     }
@@ -913,6 +1064,8 @@ public class DataSchema {
           return JSON;
         case BYTES:
           return BYTES;
+        case UUID:
+          return UUID;
         case MAP:
           return MAP;
         case OPEN_STRUCT:
@@ -944,6 +1097,8 @@ public class DataSchema {
           return STRING_ARRAY;
         case BYTES:
           return BYTES_ARRAY;
+        case UUID:
+          return UUID_ARRAY;
         default:
           throw new IllegalStateException("Unsupported data type: " + 
dataType);
       }
@@ -973,6 +1128,8 @@ public class DataSchema {
           return PinotDataType.JSON;
         case BYTES:
           return PinotDataType.BYTES;
+        case UUID:
+          return PinotDataType.UUID;
         case MAP:
           return PinotDataType.MAP;
         case OBJECT:
@@ -1000,6 +1157,13 @@ public class DataSchema {
       }
     }
 
+    /// Renders a single UUID as its canonical lowercase RFC 4122 string. 
Accepts every representation
+    /// [UuidUtils#toUUID(Object)] does: `UUID`, `byte[]`, [ByteArray] and 
`CharSequence`. A non-canonical (e.g.
+    /// upper-case) string input is re-canonicalized rather than passed 
through.
+    private static String formatUuid(Object value) {
+      return UuidUtils.toUUID(value).toString();
+    }
+
     public abstract RelDataType toType(RelDataTypeFactory typeFactory);
   }
 }
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/response/encoder/ArrowResponseEncoderTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/common/response/encoder/ArrowResponseEncoderTest.java
index 0d65e3e9f0b..ea141a29398 100644
--- 
a/pinot-common/src/test/java/org/apache/pinot/common/response/encoder/ArrowResponseEncoderTest.java
+++ 
b/pinot-common/src/test/java/org/apache/pinot/common/response/encoder/ArrowResponseEncoderTest.java
@@ -23,15 +23,19 @@ import java.math.BigDecimal;
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 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.spi.utils.UuidUtils;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.fail;
 
 
@@ -114,6 +118,72 @@ public class ArrowResponseEncoderTest {
     }
   }
 
+  @Test
+  public void testEncodeDecodeUuidColumn()
+      throws IOException {
+    DataSchema schema = new DataSchema(new String[]{"uuidCol"}, new 
ColumnDataType[]{ColumnDataType.UUID});
+    List<Object[]> rows = Arrays.asList(
+        new Object[]{"550e8400-e29b-41d4-a716-446655440000"},
+        new Object[]{"f81d4fae-7dec-11d0-a765-00a0c91e6bf6"}
+    );
+
+    ResultTable resultTable = new ResultTable(schema, rows);
+    ArrowResponseEncoder encoder = new ArrowResponseEncoder();
+    byte[] encodedBytes = encoder.encodeResultTable(resultTable, 0, 
rows.size());
+    ResultTable decodedTable = encoder.decodeResultTable(encodedBytes, 
rows.size(), schema);
+
+    assertEquals(decodedTable.getRows().size(), rows.size(), "Row count should 
match");
+    for (int i = 0; i < rows.size(); i++) {
+      assertEquals(decodedTable.getRows().get(i)[0], rows.get(i)[0], "UUID row 
" + i + " should match");
+    }
+  }
+
+  /// Mirrors the real broker path: a UUID column is rendered to its canonical 
string *before* it reaches the
+  /// encoder -- via `convertAndFormat` in the single-stage engine, and 
`format(toExternal(..))` in the multi-stage
+  /// engine (see QueryDispatcher#toExternalList). The encoder therefore only 
ever sees Strings in this group.
+  @Test
+  public void testEncodeDecodeUuidColumnRenderedFromInternalValue()
+      throws IOException {
+    String uuidValue = "550e8400-e29b-41d4-a716-446655440000";
+    DataSchema schema = new DataSchema(new String[]{"uuidCol", "cnt"},
+        new ColumnDataType[]{ColumnDataType.UUID, ColumnDataType.LONG});
+    Object internalValue = 
ColumnDataType.UUID.toInternal(UUID.fromString(uuidValue));
+    List<Object[]> rows =
+        Collections.singletonList(new 
Object[]{ColumnDataType.UUID.convertAndFormat(internalValue), 3L});
+
+    ResultTable resultTable = new ResultTable(schema, rows);
+    ArrowResponseEncoder encoder = new ArrowResponseEncoder();
+    byte[] encodedBytes = encoder.encodeResultTable(resultTable, 0, 
rows.size());
+    ResultTable decodedTable = encoder.decodeResultTable(encodedBytes, 
rows.size(), schema);
+
+    assertEquals(decodedTable.getRows().size(), 1, "Row count should match");
+    assertEquals(decodedTable.getRows().get(0)[0], uuidValue, "UUID value 
should round-trip as canonical string");
+    assertEquals(decodedTable.getRows().get(0)[1], 3L, "Non-UUID columns 
should be preserved");
+  }
+
+  @Test
+  public void testEncodeDecodeUuidColumnWithNulls()
+      throws IOException {
+    String uuidValue = "550e8400-e29b-41d4-a716-446655440000";
+    DataSchema schema = new DataSchema(new String[]{"uuidCol", "uuidArrayCol"},
+        new ColumnDataType[]{ColumnDataType.UUID, ColumnDataType.UUID_ARRAY});
+    List<Object[]> rows = Arrays.asList(
+        new Object[]{uuidValue, new String[]{uuidValue}},
+        new Object[]{null, null}
+    );
+
+    ResultTable resultTable = new ResultTable(schema, rows);
+    ArrowResponseEncoder encoder = new ArrowResponseEncoder();
+    byte[] encodedBytes = encoder.encodeResultTable(resultTable, 0, 
rows.size());
+    ResultTable decodedTable = encoder.decodeResultTable(encodedBytes, 
rows.size(), schema);
+
+    assertEquals(decodedTable.getRows().size(), 2, "Row count should match");
+    assertEquals(decodedTable.getRows().get(0)[0], uuidValue, "Non-null UUID 
should round-trip");
+    assertEquals(decodedTable.getRows().get(0)[1], new String[]{uuidValue}, 
"Non-null UUID array should round-trip");
+    assertNull(decodedTable.getRows().get(1)[0], "Null UUID should round-trip 
as null");
+    assertNull(decodedTable.getRows().get(1)[1], "Null UUID array should 
round-trip as null");
+  }
+
   @Test
   public void testEncodeDecodeAllDataTypes()
       throws IOException {
@@ -122,7 +192,7 @@ public class ArrowResponseEncoderTest {
         "intCol", "longCol", "floatCol", "doubleCol", "bigDecimalCol", 
"booleanCol", "timestampCol",
         "stringCol", "jsonCol", "mapCol", "bytesCol", "objectCol", 
"intArrayCol", "longArrayCol",
         "floatArrayCol", "doubleArrayCol", "booleanArrayCol", 
"timestampArrayCol", "stringArrayCol",
-        "bytesArrayCol", "unknownCol"
+        "bytesArrayCol", "uuidArrayCol", "unknownCol"
     };
 
     DataSchema.ColumnDataType[] columnTypes = {
@@ -146,6 +216,7 @@ public class ArrowResponseEncoderTest {
         ColumnDataType.TIMESTAMP_ARRAY,
         ColumnDataType.STRING_ARRAY,
         ColumnDataType.BYTES_ARRAY,
+        ColumnDataType.UUID_ARRAY,
         ColumnDataType.UNKNOWN
     };
 
@@ -178,6 +249,10 @@ public class ArrowResponseEncoderTest {
     byte[][] bytesArrayVal = new byte[][]{
         new byte[]{1, 2}, new byte[]{3, 4}
     };
+    byte[][] uuidArrayVal = new byte[][]{
+        UuidUtils.toBytes("550e8400-e29b-41d4-a716-446655440000"),
+        UuidUtils.toBytes("550e8400-e29b-41d4-a716-446655440001")
+    };
     Object unknownVal = null;  // UNKNOWN is represented as null in this 
example.
 
     // Build a single row that contains all the above values.
@@ -185,7 +260,7 @@ public class ArrowResponseEncoderTest {
     Object[] row = new Object[]{
         intVal, longVal, floatVal, doubleVal, bigDecimalVal, booleanVal, 
timestampVal, stringVal,
         jsonVal, mapVal, bytesVal, objectVal, intArrayVal, longArrayVal, 
floatArrayVal, doubleArrayVal,
-        booleanArrayVal, timestampArrayVal, stringArrayVal, bytesArrayVal, 
unknownVal
+        booleanArrayVal, timestampArrayVal, stringArrayVal, bytesArrayVal, 
uuidArrayVal, unknownVal
     };
     for (int i = 0; i < row.length; i++) {
       row[i] = columnTypes[i].format(row[i]); // Convert to internal 
representation.
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/response/encoder/JsonResponseEncoderTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/common/response/encoder/JsonResponseEncoderTest.java
index f23088fca01..4625d618be1 100644
--- 
a/pinot-common/src/test/java/org/apache/pinot/common/response/encoder/JsonResponseEncoderTest.java
+++ 
b/pinot-common/src/test/java/org/apache/pinot/common/response/encoder/JsonResponseEncoderTest.java
@@ -26,12 +26,15 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 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.spi.utils.UuidUtils;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.fail;
 
 public class JsonResponseEncoderTest {
@@ -110,6 +113,46 @@ public class JsonResponseEncoderTest {
     }
   }
 
+  @Test
+  public void testEncodeDecodeUuidColumn() throws IOException {
+    DataSchema schema = new DataSchema(
+        new String[] {"uuidCol"},
+        new ColumnDataType[] {ColumnDataType.UUID});
+    String uuidValue = "550e8400-e29b-41d4-a716-446655440000";
+
+    List<Object[]> rows = new ArrayList<>();
+    rows.add(new Object[] 
{ColumnDataType.UUID.format(UUID.fromString(uuidValue))});
+
+    ResultTable resultTable = new ResultTable(schema, rows);
+    JsonResponseEncoder encoder = new JsonResponseEncoder();
+
+    byte[] encodedBytes = encoder.encodeResultTable(resultTable, 0, 
rows.size());
+    ResultTable decodedTable = encoder.decodeResultTable(encodedBytes, 
rows.size(), schema);
+
+    assertEquals(decodedTable.getRows().size(), 1, "Row count should be 1");
+    assertEquals(decodedTable.getRows().get(0)[0], uuidValue, "UUID value 
should round-trip as canonical string");
+  }
+
+  @Test
+  public void testEncodeDecodeUuidColumnWithNulls() throws IOException {
+    String uuidValue = "550e8400-e29b-41d4-a716-446655440000";
+    DataSchema schema = new DataSchema(new String[] {"uuidCol"}, new 
ColumnDataType[] {ColumnDataType.UUID});
+
+    List<Object[]> rows = new ArrayList<>();
+    rows.add(new Object[] {uuidValue});
+    rows.add(new Object[] {null});
+
+    ResultTable resultTable = new ResultTable(schema, rows);
+    JsonResponseEncoder encoder = new JsonResponseEncoder();
+
+    byte[] encodedBytes = encoder.encodeResultTable(resultTable, 0, 
rows.size());
+    ResultTable decodedTable = encoder.decodeResultTable(encodedBytes, 
rows.size(), schema);
+
+    assertEquals(decodedTable.getRows().size(), 2, "Row count should be 2");
+    assertEquals(decodedTable.getRows().get(0)[0], uuidValue, "Non-null UUID 
should round-trip");
+    assertNull(decodedTable.getRows().get(1)[0], "Null UUID should round-trip 
as null");
+  }
+
   @Test
   public void testEncodeDecodeAllDataTypes() throws IOException {
     // Define the column names and corresponding data types.
@@ -117,7 +160,7 @@ public class JsonResponseEncoderTest {
         "intCol", "longCol", "floatCol", "doubleCol", "bigDecimalCol", 
"booleanCol", "timestampCol",
         "stringCol", "jsonCol", "mapCol", "bytesCol", "objectCol", 
"intArrayCol", "longArrayCol",
         "floatArrayCol", "doubleArrayCol", "booleanArrayCol", 
"timestampArrayCol", "stringArrayCol",
-        "bytesArrayCol", "unknownCol"
+        "bytesArrayCol", "uuidArrayCol", "unknownCol"
     };
 
     DataSchema.ColumnDataType[] columnTypes = {
@@ -141,6 +184,7 @@ public class JsonResponseEncoderTest {
         ColumnDataType.TIMESTAMP_ARRAY,
         ColumnDataType.STRING_ARRAY,
         ColumnDataType.BYTES_ARRAY,
+        ColumnDataType.UUID_ARRAY,
         ColumnDataType.UNKNOWN
     };
 
@@ -173,6 +217,10 @@ public class JsonResponseEncoderTest {
     byte[][] bytesArrayVal = new byte[][] {
         new byte[] {1, 2}, new byte[] {3, 4}
     };
+    byte[][] uuidArrayVal = new byte[][] {
+        UuidUtils.toBytes("550e8400-e29b-41d4-a716-446655440000"),
+        UuidUtils.toBytes("550e8400-e29b-41d4-a716-446655440001")
+    };
     Object unknownVal = null;  // UNKNOWN is represented as null in this 
example.
 
     // Build a single row that contains all the above values.
@@ -180,7 +228,7 @@ public class JsonResponseEncoderTest {
     Object[] row = new Object[] {
         intVal, longVal, floatVal, doubleVal, bigDecimalVal, booleanVal, 
timestampVal, stringVal,
         jsonVal, mapVal, bytesVal, objectVal, intArrayVal, longArrayVal, 
floatArrayVal, doubleArrayVal,
-        booleanArrayVal, timestampArrayVal, stringArrayVal, bytesArrayVal, 
unknownVal
+        booleanArrayVal, timestampArrayVal, stringArrayVal, bytesArrayVal, 
uuidArrayVal, unknownVal
     };
 
     // Convert each value using the schema's formatting (if needed).
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 3a22b1d30cc..ba3bdceef4e 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
@@ -21,8 +21,11 @@ package org.apache.pinot.common.utils;
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
 import java.sql.Timestamp;
+import java.util.Locale;
 import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.utils.ByteArray;
 import org.apache.pinot.spi.utils.BytesUtils;
+import org.apache.pinot.spi.utils.UuidUtils;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -31,14 +34,19 @@ import static 
org.apache.pinot.common.utils.DataSchema.ColumnDataType.*;
 
 public class DataSchemaTest {
   private static final String[] COLUMN_NAMES = {
-      "int", "long", "float", "double", "string", "object", "int_array", 
"long_array", "float_array", "double_array",
-      "string_array", "boolean_array", "timestamp_array", "bytes_array"
+      "int", "long", "float", "double", "string", "uuid", "object", 
"int_array", "long_array", "float_array",
+      "double_array", "string_array", "boolean_array", "timestamp_array", 
"bytes_array", "uuid_array"
   };
   private static final int NUM_COLUMNS = COLUMN_NAMES.length;
   private static final DataSchema.ColumnDataType[] COLUMN_DATA_TYPES = {
-      INT, LONG, FLOAT, DOUBLE, STRING, OBJECT, INT_ARRAY, LONG_ARRAY, 
FLOAT_ARRAY, DOUBLE_ARRAY, STRING_ARRAY,
-      BOOLEAN_ARRAY, TIMESTAMP_ARRAY, BYTES_ARRAY
+      INT, LONG, FLOAT, DOUBLE, STRING, UUID, OBJECT, INT_ARRAY, LONG_ARRAY, 
FLOAT_ARRAY, DOUBLE_ARRAY, STRING_ARRAY,
+      BOOLEAN_ARRAY, TIMESTAMP_ARRAY, BYTES_ARRAY, UUID_ARRAY
   };
+  private static final String UUID_VALUE = 
"550e8400-e29b-41d4-a716-446655440000";
+  private static final String UUID_VALUE_2 = 
"550e8400-e29b-41d4-a716-446655440001";
+  // Fully qualified: the static ColumnDataType.* import below binds the 
simple name UUID to the enum constant.
+  private static final java.util.UUID JAVA_UUID = 
java.util.UUID.fromString(UUID_VALUE);
+  private static final java.util.UUID JAVA_UUID_2 = 
java.util.UUID.fromString(UUID_VALUE_2);
 
   @Test
   public void testGetters() {
@@ -71,9 +79,10 @@ public class DataSchemaTest {
   public void testToString() {
     DataSchema dataSchema = new DataSchema(COLUMN_NAMES, COLUMN_DATA_TYPES);
     Assert.assertEquals(dataSchema.toString(),
-        
"[int(INT),long(LONG),float(FLOAT),double(DOUBLE),string(STRING),object(OBJECT),int_array(INT_ARRAY),"
-            + 
"long_array(LONG_ARRAY),float_array(FLOAT_ARRAY),double_array(DOUBLE_ARRAY),string_array(STRING_ARRAY),"
-            + 
"boolean_array(BOOLEAN_ARRAY),timestamp_array(TIMESTAMP_ARRAY),bytes_array(BYTES_ARRAY)]");
+        
"[int(INT),long(LONG),float(FLOAT),double(DOUBLE),string(STRING),uuid(UUID),object(OBJECT),"
+            + 
"int_array(INT_ARRAY),long_array(LONG_ARRAY),float_array(FLOAT_ARRAY),double_array(DOUBLE_ARRAY),"
+            + 
"string_array(STRING_ARRAY),boolean_array(BOOLEAN_ARRAY),timestamp_array(TIMESTAMP_ARRAY),"
+            + "bytes_array(BYTES_ARRAY),uuid_array(UUID_ARRAY)]");
   }
 
   @Test
@@ -115,6 +124,16 @@ public class DataSchemaTest {
     Assert.assertFalse(STRING.isCompatible(STRING_ARRAY));
     Assert.assertFalse(STRING.isCompatible(BYTES_ARRAY));
 
+    Assert.assertFalse(UUID.isNumber());
+    Assert.assertFalse(UUID.isWholeNumber());
+    Assert.assertFalse(UUID.isArray());
+    Assert.assertFalse(UUID.isNumberArray());
+    Assert.assertFalse(UUID.isWholeNumberArray());
+    Assert.assertFalse(UUID.isCompatible(DOUBLE));
+    Assert.assertTrue(UUID.isCompatible(UUID));
+    Assert.assertFalse(UUID.isCompatible(BYTES));
+    Assert.assertFalse(UUID.isCompatible(STRING));
+
     Assert.assertFalse(OBJECT.isNumber());
     Assert.assertFalse(OBJECT.isWholeNumber());
     Assert.assertFalse(OBJECT.isArray());
@@ -154,7 +173,7 @@ public class DataSchemaTest {
     }
 
     for (DataSchema.ColumnDataType columnDataType : new 
DataSchema.ColumnDataType[]{
-        STRING_ARRAY, BOOLEAN_ARRAY, TIMESTAMP_ARRAY, BYTES_ARRAY
+        STRING_ARRAY, BOOLEAN_ARRAY, TIMESTAMP_ARRAY, BYTES_ARRAY, UUID_ARRAY
     }) {
       Assert.assertFalse(columnDataType.isNumber());
       Assert.assertFalse(columnDataType.isWholeNumber());
@@ -178,6 +197,8 @@ public class DataSchemaTest {
     Assert.assertEquals(fromDataType(FieldSpec.DataType.DOUBLE, false), 
DOUBLE_ARRAY);
     Assert.assertEquals(fromDataType(FieldSpec.DataType.STRING, true), STRING);
     Assert.assertEquals(fromDataType(FieldSpec.DataType.STRING, false), 
STRING_ARRAY);
+    Assert.assertEquals(fromDataType(FieldSpec.DataType.UUID, true), UUID);
+    Assert.assertEquals(fromDataType(FieldSpec.DataType.UUID, false), 
UUID_ARRAY);
     Assert.assertEquals(fromDataType(FieldSpec.DataType.BOOLEAN, false), 
BOOLEAN_ARRAY);
     Assert.assertEquals(fromDataType(FieldSpec.DataType.TIMESTAMP, false), 
TIMESTAMP_ARRAY);
     Assert.assertEquals(fromDataType(FieldSpec.DataType.BYTES, false), 
BYTES_ARRAY);
@@ -186,7 +207,58 @@ public class DataSchemaTest {
     Assert.assertEquals(BIG_DECIMAL.format(bigDecimalValue), 
bigDecimalValue.toPlainString());
     Timestamp timestampValue = new Timestamp(1234567890123L);
     Assert.assertEquals(TIMESTAMP.format(timestampValue), 
timestampValue.toString());
+    ByteArray uuidValue = new ByteArray(UuidUtils.toBytes(UUID_VALUE));
+    Assert.assertEquals(UUID.convert(uuidValue), JAVA_UUID);
+    Assert.assertEquals(UUID.format(uuidValue), UUID_VALUE);
+    Assert.assertEquals(UUID.convertAndFormat(uuidValue), UUID_VALUE);
+    // format() also accepts the external form and re-canonicalizes 
non-canonical strings.
+    Assert.assertEquals(UUID.format(JAVA_UUID), UUID_VALUE);
+    Assert.assertEquals(UUID.format(UUID_VALUE.toUpperCase(Locale.ROOT)), 
UUID_VALUE);
+    byte[][] uuidArrayBytesValue = {UuidUtils.toBytes(UUID_VALUE), 
UuidUtils.toBytes(UUID_VALUE_2)};
+    ByteArray[] uuidArrayValue = (ByteArray[]) UUID_ARRAY.toInternal(new 
String[]{UUID_VALUE, UUID_VALUE_2});
+    java.util.UUID[] expectedUuidArray = {JAVA_UUID, JAVA_UUID_2};
+    String[] expectedFormatted = {UUID_VALUE, UUID_VALUE_2};
+    Assert.assertEquals(UUID_ARRAY.toExternal(uuidArrayValue), 
expectedUuidArray);
+    Assert.assertEquals(UUID_ARRAY.toExternal(uuidArrayBytesValue), 
expectedUuidArray);
+    Assert.assertEquals(UUID_ARRAY.convert(uuidArrayValue), expectedUuidArray);
+    Assert.assertEquals(UUID_ARRAY.toInternal(expectedUuidArray), 
uuidArrayValue);
+    Assert.assertEquals(UUID_ARRAY.toInternal(uuidArrayBytesValue), 
uuidArrayValue);
+    Assert.assertEquals(UUID_ARRAY.format(uuidArrayBytesValue), 
expectedFormatted);
+    Assert.assertEquals(UUID_ARRAY.format(expectedUuidArray), 
expectedFormatted);
+    Assert.assertEquals(UUID_ARRAY.format(uuidArrayValue), expectedFormatted);
+    Assert.assertEquals(UUID_ARRAY.convertAndFormat(uuidArrayValue), 
expectedFormatted);
+    Assert.assertEquals(UUID_ARRAY.convertAndFormat(uuidArrayBytesValue), 
expectedFormatted);
     byte[] bytesValue = {12, 34, 56};
     Assert.assertEquals(BYTES.format(bytesValue), 
BytesUtils.toHexString(bytesValue));
   }
+
+  /// 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,
+  /// GroupByResultsBlock, GroupByDataTableReducer) would silently write the 
wrong placeholder.
+  @Test
+  public void testNullPlaceholderMatchesStoredTypeExceptUuid() {
+    for (DataSchema.ColumnDataType columnDataType : 
DataSchema.ColumnDataType.values()) {
+      DataSchema.ColumnDataType storedType = columnDataType.getStoredType();
+      if (columnDataType == UUID) {
+        Assert.assertEquals(storedType, BYTES);
+        Assert.assertEquals(columnDataType.getNullPlaceholder(), new 
ByteArray(UuidUtils.nullUuidBytes()));
+        Assert.assertNotEquals(columnDataType.getNullPlaceholder(), 
storedType.getNullPlaceholder());
+      } else {
+        Assert.assertEquals(columnDataType.getNullPlaceholder(), 
storedType.getNullPlaceholder(),
+            "Null placeholder mismatch between " + columnDataType + " and its 
stored type " + storedType);
+      }
+    }
+  }
+
+  /// The nil-UUID placeholder wraps a mutable 16-byte array, unlike every 
other placeholder (all empty or
+  /// immutable), so each call must hand back a fresh instance.
+  @Test
+  public void testUuidNullPlaceholderIsNotShared() {
+    ByteArray first = (ByteArray) UUID.getNullPlaceholder();
+    ByteArray second = (ByteArray) UUID.getNullPlaceholder();
+    Assert.assertNotSame(first, second);
+    first.getBytes()[0] = 1;
+    Assert.assertEquals(second, new ByteArray(UuidUtils.nullUuidBytes()));
+  }
 }
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java
index 3a15b30184e..6e418e572c4 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java
@@ -81,7 +81,9 @@ public class DataBlockBuilder {
     Object[] nullPlaceholders = new Object[numColumns];
     for (int colId = 0; colId < numColumns; colId++) {
       nullBitmaps[colId] = new RoaringBitmap();
-      nullPlaceholders[colId] = storedTypes[colId].getNullPlaceholder();
+      // Resolved on the logical type, not the stored type: UUID overrides 
getNullPlaceholder() to return the nil
+      // UUID, whereas its stored type BYTES would yield a zero-length 
placeholder that is not a valid UUID.
+      nullPlaceholders[colId] = 
dataSchema.getColumnDataType(colId).getNullPlaceholder();
     }
     int nullFixedBytes = numColumns * Integer.BYTES * 2;
     int rowSizeInBytes = calculateBytesPerRow(dataSchema);
@@ -254,7 +256,11 @@ public class DataBlockBuilder {
       ByteBuffer fixedSize, PagedPinotOutputStream varSize, RoaringBitmap 
nullBitmap,
       Object2IntOpenHashMap<String> dictionary, @Nullable AggregationFunction 
aggFunction)
       throws IOException {
-    ColumnDataType storedType = 
dataSchema.getColumnDataType(colId).getStoredType();
+    // Dispatch on the stored type, but read null placeholders off the logical 
type: UUID overrides
+    // getNullPlaceholder() to return the nil UUID, whereas its stored type 
BYTES would yield a zero-length
+    // placeholder that is not a valid UUID. The two agree for every other 
type (see DataSchemaTest).
+    ColumnDataType columnDataType = dataSchema.getColumnDataType(colId);
+    ColumnDataType storedType = columnDataType.getStoredType();
     int numRows = columns.get(colId).length;
 
     Object[] column = columns.get(colId);
@@ -266,7 +272,7 @@ public class DataBlockBuilder {
     switch (storedType) {
       // Single-value column
       case INT: {
-        int nullPlaceholder = (int) storedType.getNullPlaceholder();
+        int nullPlaceholder = (int) columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -281,7 +287,7 @@ public class DataBlockBuilder {
         break;
       }
       case LONG: {
-        long nullPlaceholder = (long) storedType.getNullPlaceholder();
+        long nullPlaceholder = (long) columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -296,7 +302,7 @@ public class DataBlockBuilder {
         break;
       }
       case FLOAT: {
-        float nullPlaceholder = (float) storedType.getNullPlaceholder();
+        float nullPlaceholder = (float) columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -311,7 +317,7 @@ public class DataBlockBuilder {
         break;
       }
       case DOUBLE: {
-        double nullPlaceholder = (double) storedType.getNullPlaceholder();
+        double nullPlaceholder = (double) columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -326,7 +332,7 @@ public class DataBlockBuilder {
         break;
       }
       case BIG_DECIMAL: {
-        BigDecimal nullPlaceholder = (BigDecimal) 
storedType.getNullPlaceholder();
+        BigDecimal nullPlaceholder = (BigDecimal) 
columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -342,7 +348,7 @@ public class DataBlockBuilder {
       }
       case STRING: {
         ToIntFunction<String> didSupplier = k -> dictionary.size();
-        int nullPlaceHolder = dictionary.computeIfAbsent((String) 
storedType.getNullPlaceholder(), didSupplier);
+        int nullPlaceHolder = dictionary.computeIfAbsent((String) 
columnDataType.getNullPlaceholder(), didSupplier);
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -358,7 +364,7 @@ public class DataBlockBuilder {
         break;
       }
       case BYTES: {
-        ByteArray nullPlaceholder = (ByteArray) 
storedType.getNullPlaceholder();
+        ByteArray nullPlaceholder = (ByteArray) 
columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -373,7 +379,7 @@ public class DataBlockBuilder {
         break;
       }
       case MAP: {
-        Map nullPlaceholder = (Map) storedType.getNullPlaceholder();
+        Map nullPlaceholder = (Map) columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -389,7 +395,7 @@ public class DataBlockBuilder {
       }
       // Multi-value column
       case INT_ARRAY: {
-        int[] nullPlaceholder = (int[]) storedType.getNullPlaceholder();
+        int[] nullPlaceholder = (int[]) columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -404,7 +410,7 @@ public class DataBlockBuilder {
         break;
       }
       case LONG_ARRAY: {
-        long[] nullPlaceholder = (long[]) storedType.getNullPlaceholder();
+        long[] nullPlaceholder = (long[]) columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -419,7 +425,7 @@ public class DataBlockBuilder {
         break;
       }
       case FLOAT_ARRAY: {
-        float[] nullPlaceholder = (float[]) storedType.getNullPlaceholder();
+        float[] nullPlaceholder = (float[]) 
columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -434,7 +440,7 @@ public class DataBlockBuilder {
         break;
       }
       case DOUBLE_ARRAY: {
-        double[] nullPlaceholder = (double[]) storedType.getNullPlaceholder();
+        double[] nullPlaceholder = (double[]) 
columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -449,7 +455,7 @@ public class DataBlockBuilder {
         break;
       }
       case BIG_DECIMAL_ARRAY: {
-        BigDecimal[] nullPlaceholder = (BigDecimal[]) 
storedType.getNullPlaceholder();
+        BigDecimal[] nullPlaceholder = (BigDecimal[]) 
columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -464,7 +470,7 @@ public class DataBlockBuilder {
         break;
       }
       case STRING_ARRAY: {
-        String[] nullPlaceholder = (String[]) storedType.getNullPlaceholder();
+        String[] nullPlaceholder = (String[]) 
columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
@@ -479,7 +485,7 @@ public class DataBlockBuilder {
         break;
       }
       case BYTES_ARRAY: {
-        ByteArray[] nullPlaceholder = (ByteArray[]) 
storedType.getNullPlaceholder();
+        ByteArray[] nullPlaceholder = (ByteArray[]) 
columnDataType.getNullPlaceholder();
         interruptableLoop(0, numRows, interruptableLoopStep, (start, end) -> {
           for (int rowId = start; rowId < end; rowId++) {
             Object value = column[rowId];
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java
index 6cfc8511818..0a5565be7b9 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java
@@ -220,7 +220,9 @@ public class GroupByResultsBlock extends BaseResultsBlock {
       Object[] nullPlaceholders = new Object[numColumns];
       for (int colId = 0; colId < numColumns; colId++) {
         nullBitmaps[colId] = new RoaringBitmap();
-        nullPlaceholders[colId] = 
storedColumnDataTypes[colId].getNullPlaceholder();
+        // Resolved on the logical type, not the stored type: UUID overrides 
getNullPlaceholder() to return the nil
+        // UUID, whereas its stored type BYTES would yield a zero-length 
placeholder that is not a valid UUID.
+        nullPlaceholders[colId] = 
_dataSchema.getColumnDataType(colId).getNullPlaceholder();
       }
       int rowId = 0;
       while (iterator.hasNext()) {
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
index bfbd8bb8486..85b8f614f3d 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java
@@ -564,7 +564,9 @@ public class GroupByDataTableReducer implements 
DataTableReducer {
       Object[] nullPlaceholders = new Object[_numColumns];
       for (int colId = 0; colId < _numColumns; colId++) {
         nullBitmaps[colId] = new RoaringBitmap();
-        nullPlaceholders[colId] = 
storedColumnDataTypes[colId].getNullPlaceholder();
+        // Resolved on the logical type, not the stored type: UUID overrides 
getNullPlaceholder() to return the nil
+        // UUID, whereas its stored type BYTES would yield a zero-length 
placeholder that is not a valid UUID.
+        nullPlaceholders[colId] = 
dataSchema.getColumnDataType(colId).getNullPlaceholder();
       }
       int rowId = 0;
       while (iterator.hasNext()) {
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockBuilderTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockBuilderTest.java
index f7206f59189..24e4c03b11c 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockBuilderTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockBuilderTest.java
@@ -32,6 +32,7 @@ import org.apache.pinot.common.utils.DataSchema;
 import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
 import org.apache.pinot.core.query.aggregation.function.AggregationFunction;
 import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.UuidUtils;
 import org.roaringbitmap.RoaringBitmap;
 import org.testng.Assert;
 import org.testng.annotations.DataProvider;
@@ -194,6 +195,28 @@ public class DataBlockBuilderTest {
     runColumnBlockTest(type, 25_000);
   }
 
+  /// A null in a UUID column must serialize as the nil UUID, not as the 
zero-length placeholder its stored type
+  /// (BYTES) supplies. The value is normally masked by the null bitmap, but 
it must still decode as a valid 16-byte
+  /// UUID for any consumer that renders the raw column, and 
[UuidUtils#toString] rejects any other width.
+  @Test
+  void testUuidNullPlaceholderIsNilUuid()
+      throws IOException {
+    ByteArray uuid = new 
ByteArray(UuidUtils.toBytes("550e8400-e29b-41d4-a716-446655440000"));
+    DataSchema dataSchema = new DataSchema(new String[]{"uuidCol"}, new 
ColumnDataType[]{ColumnDataType.UUID});
+    Object[] column = {uuid, null};
+    List<Object[]> rows = List.of(new Object[]{uuid}, new Object[]{null});
+
+    List<DataBlock> blocks = List.of(DataBlockBuilder.buildFromRows(rows, 
dataSchema),
+        DataBlockBuilder.buildFromColumns(List.<Object[]>of(column), 
dataSchema));
+    for (DataBlock block : blocks) {
+      assertEquals(block.getNumberOfRows(), 2);
+      assertEquals(new ByteArray(block.getBytes(0, 0).getBytes()), uuid);
+      // Row 1 is null: the bitmap flags it, and the placeholder underneath 
still renders as the nil UUID.
+      assertEquals(block.getNullRowIds(0), RoaringBitmap.bitmapOf(1));
+      assertEquals(UuidUtils.toString(block.getBytes(1, 0).getBytes()), 
"00000000-0000-0000-0000-000000000000");
+    }
+  }
+
   private void runColumnBlockTest(ColumnDataType type, int numRows)
       throws IOException {
     Object[] column = generateColumns(type, numRows);
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTestUtils.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTestUtils.java
index c96a21b5e86..a9fd19f0900 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTestUtils.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTestUtils.java
@@ -24,11 +24,13 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
+import java.util.UUID;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.pinot.common.datablock.DataBlock;
 import org.apache.pinot.common.utils.DataSchema;
 import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
 import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.UuidUtils;
 import org.roaringbitmap.RoaringBitmap;
 
 
@@ -73,6 +75,9 @@ public class DataBlockTestUtils {
         case BYTES:
           row[colId] = new 
ByteArray(RandomStringUtils.secure().next(RANDOM.nextInt(20)).getBytes());
           break;
+        case UUID:
+          row[colId] = new ByteArray(UuidUtils.toBytes(new 
UUID(RANDOM.nextLong(), RANDOM.nextLong())));
+          break;
         case INT_ARRAY:
           int length = RANDOM.nextInt(ARRAY_SIZE);
           int[] intArray = new int[length];
@@ -147,6 +152,14 @@ public class DataBlockTestUtils {
           }
           row[colId] = bytesArray;
           break;
+        case UUID_ARRAY:
+          length = RANDOM.nextInt(ARRAY_SIZE);
+          ByteArray[] uuidArray = new ByteArray[length];
+          for (int i = 0; i < length; i++) {
+            uuidArray[i] = new ByteArray(UuidUtils.toBytes(new 
UUID(RANDOM.nextLong(), RANDOM.nextLong())));
+          }
+          row[colId] = uuidArray;
+          break;
         case MAP:
           length = RANDOM.nextInt(ARRAY_SIZE);
           Map<String, Object> map = new HashMap<>();
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/common/datatable/DataTableSerDeTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/common/datatable/DataTableSerDeTest.java
index 7f416c9f414..f2ac8811035 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/common/datatable/DataTableSerDeTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/common/datatable/DataTableSerDeTest.java
@@ -25,6 +25,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Random;
+import java.util.UUID;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.pinot.common.datatable.DataTable;
@@ -34,6 +35,7 @@ import org.apache.pinot.common.utils.DataSchema;
 import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider;
 import org.apache.pinot.spi.exception.QueryErrorCode;
 import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.UuidUtils;
 import org.roaringbitmap.RoaringBitmap;
 import org.testng.Assert;
 import org.testng.annotations.DataProvider;
@@ -60,6 +62,7 @@ public class DataTableSerDeTest {
   private static final String[] STRINGS = new String[NUM_ROWS];
   private static final String[] JSONS = new String[NUM_ROWS];
   private static final byte[][] BYTES = new byte[NUM_ROWS][];
+  private static final byte[][] UUIDS = new byte[NUM_ROWS][];
   private static final Object[] OBJECTS = new Object[NUM_ROWS];
   private static final int[][] INT_ARRAYS = new int[NUM_ROWS][];
   private static final long[][] LONG_ARRAYS = new long[NUM_ROWS][];
@@ -69,6 +72,7 @@ public class DataTableSerDeTest {
   private static final long[][] TIMESTAMP_ARRAYS = new long[NUM_ROWS][];
   private static final String[][] STRING_ARRAYS = new String[NUM_ROWS][];
   private static final ByteArray[][] BYTES_ARRAYS = new ByteArray[NUM_ROWS][];
+  private static final ByteArray[][] UUID_ARRAYS = new ByteArray[NUM_ROWS][];
   private static final BigDecimal[][] BIG_DECIMAL_ARRAYS = new 
BigDecimal[NUM_ROWS][];
   private static final Map<String, Object>[] MAPS = new Map[NUM_ROWS];
 
@@ -367,6 +371,11 @@ public class DataTableSerDeTest {
             BYTES[rowId] = isNull ? new byte[0] : 
RandomStringUtils.secure().next(RANDOM.nextInt(20)).getBytes();
             dataTableBuilder.setColumn(colId, new ByteArray(BYTES[rowId]));
             break;
+          case UUID:
+            UUIDS[rowId] = isNull ? UuidUtils.nullUuidBytes()
+                : UuidUtils.toBytes(new UUID(RANDOM.nextLong(), 
RANDOM.nextLong()));
+            dataTableBuilder.setColumn(colId, new ByteArray(UUIDS[rowId]));
+            break;
           case INT_ARRAY:
             int length = RANDOM.nextInt(20);
             int[] intArray = new int[length];
@@ -440,6 +449,15 @@ public class DataTableSerDeTest {
             BYTES_ARRAYS[rowId] = bytesArray;
             dataTableBuilder.setColumn(colId, bytesArray);
             break;
+          case UUID_ARRAY:
+            length = RANDOM.nextInt(20);
+            ByteArray[] uuidArray = new ByteArray[length];
+            for (int i = 0; i < length; i++) {
+              uuidArray[i] = new ByteArray(UuidUtils.toBytes(new 
UUID(RANDOM.nextLong(), RANDOM.nextLong())));
+            }
+            UUID_ARRAYS[rowId] = uuidArray;
+            dataTableBuilder.setColumn(colId, uuidArray);
+            break;
           case STRING_ARRAY:
             length = RANDOM.nextInt(20);
             String[] stringArray = new String[length];
@@ -520,6 +538,10 @@ public class DataTableSerDeTest {
             Assert.assertEquals(newDataTable.getBytes(rowId, 
colId).getBytes(), isNull ? new byte[0] : BYTES[rowId],
                 ERROR_MESSAGE);
             break;
+          case UUID:
+            Assert.assertEquals(newDataTable.getBytes(rowId, colId).getBytes(),
+                isNull ? UuidUtils.nullUuidBytes() : UUIDS[rowId], 
ERROR_MESSAGE);
+            break;
           case INT_ARRAY:
             Assert.assertTrue(Arrays.equals(newDataTable.getIntArray(rowId, 
colId), INT_ARRAYS[rowId]), ERROR_MESSAGE);
             break;
@@ -551,6 +573,10 @@ public class DataTableSerDeTest {
             Assert.assertTrue(Arrays.equals(newDataTable.getBytesArray(rowId, 
colId), BYTES_ARRAYS[rowId]),
                 ERROR_MESSAGE);
             break;
+          case UUID_ARRAY:
+            Assert.assertTrue(Arrays.equals(newDataTable.getBytesArray(rowId, 
colId), UUID_ARRAYS[rowId]),
+                ERROR_MESSAGE);
+            break;
           case STRING_ARRAY:
             Assert.assertTrue(Arrays.equals(newDataTable.getStringArray(rowId, 
colId), STRING_ARRAYS[rowId]),
                 ERROR_MESSAGE);
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/query/selection/SelectionOperatorUtilsTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/query/selection/SelectionOperatorUtilsTest.java
index 196f4dd168e..1df11e8cf86 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/query/selection/SelectionOperatorUtilsTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/query/selection/SelectionOperatorUtilsTest.java
@@ -18,17 +18,23 @@
  */
 package org.apache.pinot.core.query.selection;
 
+import java.util.Collections;
 import org.apache.commons.lang3.tuple.Pair;
+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.core.query.request.context.QueryContext;
 import 
org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils;
+import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.BytesUtils;
+import org.apache.pinot.spi.utils.UuidUtils;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
 
 
 public class SelectionOperatorUtilsTest {
+  private static final String UUID_VALUE = 
"550e8400-e29b-41d4-a716-446655440000";
 
   @Test
   public void testGetResultTableColumnIndices() {
@@ -207,4 +213,21 @@ public class SelectionOperatorUtilsTest {
             ColumnDataType.STRING, ColumnDataType.STRING, ColumnDataType.STRING
         }));
   }
+
+  @Test
+  public void testRenderResultTableWithoutOrderingFormatsUUIDAndBytes() {
+    byte[] bytesValue = new byte[]{0x01, 0x23, 0x45};
+    DataSchema dataSchema = new DataSchema(new String[]{"uuidCol", "bytesCol"},
+        new ColumnDataType[]{ColumnDataType.UUID, ColumnDataType.BYTES});
+
+    ResultTable resultTable = 
SelectionOperatorUtils.renderResultTableWithoutOrdering(
+        Collections.singletonList(
+            new Object[]{new ByteArray(UuidUtils.toBytes(UUID_VALUE)), new 
ByteArray(bytesValue)}),
+        dataSchema,
+        new int[]{0, 1});
+
+    Object[] row = resultTable.getRows().get(0);
+    assertEquals(row[0], UUID_VALUE);
+    assertEquals(row[1], BytesUtils.toHexString(bytesValue));
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to