Copilot commented on code in PR #19073:
URL: https://github.com/apache/pinot/pull/19073#discussion_r3649704102


##########
pinot-core/src/main/java/org/apache/pinot/core/util/SegmentProcessorAvroUtils.java:
##########
@@ -51,43 +54,99 @@ public static GenericData.Record 
convertGenericRowToAvroRecord(GenericRow generi
     return convertGenericRowToAvroRecord(genericRow, reusableRecord, 
genericRow.getFieldToValueMap().keySet());
   }
 
-  /// Convert a GenericRow to an avro GenericRecord
+  /// Convert a GenericRow to an avro GenericRecord.
+  ///
+  /// Values arrive in Pinot's internal (stored) representation and are 
coordinated with the Avro field type produced
+  /// by `AvroSchemaUtil.toAvroSchema`: whatever a registered logical-type 
[Conversion] can handle is left untouched
+  /// for the writer, and only the two cases Avro cannot resolve on its own 
are fixed up here (see
+  /// [#convertValue(Schema, Object)]).
   public static GenericData.Record convertGenericRowToAvroRecord(GenericRow 
genericRow,
       GenericData.Record reusableRecord, Set<String> fields) {
     Schema avroSchema = reusableRecord.getSchema();
     for (String field : fields) {
       Object value = genericRow.getValue(field);
-      if (value instanceof Object[]) {
-        // Array elements are written as-is. For MV UUID 
(array<string{logicalType:uuid}>) the elements are the raw
-        // 16-byte values; the uuid Conversion registered on the writer's data 
model (getAvroDataModel) renders each
-        // element to its canonical string at write time.
-        reusableRecord.put(field, Arrays.asList((Object[]) value));
-      } else if (value instanceof byte[]) {
-        // A byte[] bound for a plain BYTES field must be wrapped as 
ByteBuffer (GenericDatumWriter requires it for the
-        // bytes type). A byte[] bound for a UUID field 
(string{logicalType:uuid}) is left raw so the uuid Conversion
-        // registered on the writer's data model (getAvroDataModel) renders it 
to a canonical string at write time.
-        Schema.Field avroField = avroSchema.getField(field);
-        if (avroField != null && avroField.schema().getType() == 
Schema.Type.BYTES) {
-          reusableRecord.put(field, ByteBuffer.wrap((byte[]) value));
-        } else {
-          reusableRecord.put(field, value);
-        }
-      } else {
+      Schema.Field avroField = avroSchema.getField(field);
+      if (avroField == null) {
+        // Let Avro raise its own "Not a valid schema field" error for a 
column missing from the Avro schema.
         reusableRecord.put(field, value);
+      } else {
+        reusableRecord.put(avroField.pos(), convertValue(avroField.schema(), 
value));
       }
     }
     return reusableRecord;
   }
 
-  /// Shared Avro data model with [UuidConversion] registered. Populated once 
at class initialization and never
-  /// mutated afterward (effectively immutable), so it is safe to share across 
writers.
+  /// Adapts a Pinot value to the representation the Avro writer expects for 
the given field schema, recursing into
+  /// array elements for multi-value columns.
+  @Nullable
+  private static Object convertValue(Schema fieldSchema, @Nullable Object 
value) {
+    if (value == null) {
+      return null;
+    }
+    if (value instanceof Object[]) {
+      Object[] values = (Object[]) value;
+      Schema elementSchema =
+          fieldSchema.getType() == Schema.Type.ARRAY ? 
fieldSchema.getElementType() : fieldSchema;
+      // Only BOOLEAN (stored int -> Boolean) and BYTES (byte[] -> ByteBuffer) 
element schemas can require a
+      // per-element transform. Every other MV element type is written as-is — 
INT/LONG/FLOAT/DOUBLE/STRING directly,
+      // and UUID (string element, raw byte[] rendered by the registered 
Conversion) — so hand the writer a zero-copy
+      // view over the existing array; allocating and copying a fresh list per 
row would be pure overhead on the
+      // segment-write hot path. (BIG_DECIMAL has a BYTES element schema and 
so takes the copy path below, but its
+      // BigDecimal values still pass through convertSingleValue unchanged for 
the registered Conversion.)
+      Schema.Type elementType = elementSchema.getType();
+      if (elementType != Schema.Type.BOOLEAN && elementType != 
Schema.Type.BYTES) {
+        return Arrays.asList(values);
+      }
+      List<Object> converted = new ArrayList<>(values.length);

Review Comment:
   `convertValue()` currently takes the per-element conversion path for *all* 
MV fields whose Avro element schema is `BYTES`. That’s required for plain BYTES 
(byte[] -> ByteBuffer), but it also forces an unnecessary list allocation + 
element loop for BIG_DECIMAL MV columns (`bytes{logicalType:big-decimal}`) even 
though BigDecimal values can be passed through zero-copy and handled by the 
registered `BigDecimalConversion`.
   
   Consider treating `bytes{logicalType:big-decimal}` as a zero-copy case (like 
UUID), and only allocating/converting per-element for plain `bytes` schemas.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to