sunchao commented on code in PR #5452:
URL: https://github.com/apache/datafusion-comet/pull/5452#discussion_r3845993193


##########
spark/src/main/scala/org/apache/comet/serde/literals.scala:
##########
@@ -215,4 +225,82 @@ object CometLiteral extends CometExpressionSerde[Literal] 
with Logging {
     }
     listLiteralBuilder
   }
+
+  /**
+   * True when a non-null Literal of this type is not encodable in the native 
`Literal` proto and
+   * `convert` should try to rebuild it from primitive-typed Literals. The 
native proto today
+   * carries scalars and nested `ListLiteral`s (arrays of arrays / arrays of 
scalars). It does not
+   * carry Map or Struct values, so any Literal whose type contains a 
`MapType` or a `StructType`
+   * needs to be expanded before serialization.
+   */
+  private def needsExpansion(dataType: DataType): Boolean = dataType match {
+    case _: MapType | _: StructType => true
+    case ArrayType(et, _) => needsExpansion(et)
+    case _ => false
+  }
+
+  /**
+   * True when the Literal is a non-null complex value that we can rebuild 
from primitive Literals
+   * via [[expandComplexLiteral]]. Empty top-level containers are excluded 
because a synthesized
+   * `Create[Array|Map]` with no children cannot recover the original element 
type.
+   */
+  private def canExpandComplexLiteral(expr: Literal): Boolean = {
+    val value = expr.value
+    if (value == null || !needsExpansion(expr.dataType)) return false
+    expr.dataType match {
+      case _: ArrayType => value.asInstanceOf[ArrayData].numElements() > 0
+      case _: MapType => value.asInstanceOf[MapData].numElements() > 0
+      case _: StructType => true
+      case _ => false
+    }
+  }
+
+  /**
+   * Rebuild a folded complex Literal as an equivalent tree of `CreateArray` / 
`CreateMap` /
+   * `CreateNamedStruct` over primitive-typed Literals. Callers must gate on
+   * [[canExpandComplexLiteral]] so `dataType` is one of the three complex 
cases and top-level
+   * containers are non-empty.
+   */
+  private def expandComplexLiteral(value: Any, dataType: DataType): Expression 
=
+    dataType match {
+      case ArrayType(et, _) =>
+        val arr = value.asInstanceOf[ArrayData]
+        val elems = (0 until arr.numElements()).map { i =>
+          if (arr.isNullAt(i)) Literal(null, et)
+          else asNullable(Literal(arr.get(i, et), et))
+        }
+        CreateArray(elems, useStringTypeWhenEmpty = false)
+      case MapType(kt, vt, _) =>
+        val mapData = value.asInstanceOf[MapData]
+        val keys = mapData.keyArray()
+        val vals = mapData.valueArray()
+        val children = (0 until keys.numElements()).flatMap { i =>
+          val k = if (keys.isNullAt(i)) Literal(null, kt) else 
Literal(keys.get(i, kt), kt)
+          val v =
+            if (vals.isNullAt(i)) Literal(null, vt)
+            else asNullable(Literal(vals.get(i, vt), vt))
+          Seq(k, v)
+        }
+        CreateMap(children, useStringTypeWhenEmpty = false)

Review Comment:
   [P2] Preserve existing map entries without reapplying constructor 
deduplication
   
   A folded `MapData` need not have been produced by `CreateMap`. For example, 
Spark folds `from_json('{"a":1,"a":2}', 'MAP<STRING,INT>')` into a map literal 
containing keys `[a,a]` and values `[1,2]`, and Spark/the base serializer 
execute it successfully. Rebuilding that value as `CreateMap` sends it through 
`ArrayBasedMapBuilder` again, so projecting it alongside a column from a 
Parquet table now throws `[DUPLICATED_MAP_KEY]` under the default policy. I 
reproduced the regression on Spark 3.5.9 and 4.1.3 with normal folding and a 
native Comet projection. Please preserve the literal's existing entries 
directly, or retain fallback when reconstruction would change their semantics.



##########
spark/src/main/scala/org/apache/comet/serde/literals.scala:
##########
@@ -215,4 +225,82 @@ object CometLiteral extends CometExpressionSerde[Literal] 
with Logging {
     }
     listLiteralBuilder
   }
+
+  /**
+   * True when a non-null Literal of this type is not encodable in the native 
`Literal` proto and
+   * `convert` should try to rebuild it from primitive-typed Literals. The 
native proto today
+   * carries scalars and nested `ListLiteral`s (arrays of arrays / arrays of 
scalars). It does not
+   * carry Map or Struct values, so any Literal whose type contains a 
`MapType` or a `StructType`
+   * needs to be expanded before serialization.
+   */
+  private def needsExpansion(dataType: DataType): Boolean = dataType match {
+    case _: MapType | _: StructType => true
+    case ArrayType(et, _) => needsExpansion(et)
+    case _ => false
+  }
+
+  /**
+   * True when the Literal is a non-null complex value that we can rebuild 
from primitive Literals
+   * via [[expandComplexLiteral]]. Empty top-level containers are excluded 
because a synthesized
+   * `Create[Array|Map]` with no children cannot recover the original element 
type.
+   */
+  private def canExpandComplexLiteral(expr: Literal): Boolean = {
+    val value = expr.value
+    if (value == null || !needsExpansion(expr.dataType)) return false
+    expr.dataType match {
+      case _: ArrayType => value.asInstanceOf[ArrayData].numElements() > 0
+      case _: MapType => value.asInstanceOf[MapData].numElements() > 0
+      case _: StructType => true
+      case _ => false
+    }
+  }
+
+  /**
+   * Rebuild a folded complex Literal as an equivalent tree of `CreateArray` / 
`CreateMap` /
+   * `CreateNamedStruct` over primitive-typed Literals. Callers must gate on
+   * [[canExpandComplexLiteral]] so `dataType` is one of the three complex 
cases and top-level
+   * containers are non-empty.
+   */
+  private def expandComplexLiteral(value: Any, dataType: DataType): Expression 
=
+    dataType match {
+      case ArrayType(et, _) =>
+        val arr = value.asInstanceOf[ArrayData]
+        val elems = (0 until arr.numElements()).map { i =>
+          if (arr.isNullAt(i)) Literal(null, et)
+          else asNullable(Literal(arr.get(i, et), et))
+        }
+        CreateArray(elems, useStringTypeWhenEmpty = false)
+      case MapType(kt, vt, _) =>
+        val mapData = value.asInstanceOf[MapData]
+        val keys = mapData.keyArray()
+        val vals = mapData.valueArray()
+        val children = (0 until keys.numElements()).flatMap { i =>
+          val k = if (keys.isNullAt(i)) Literal(null, kt) else 
Literal(keys.get(i, kt), kt)
+          val v =
+            if (vals.isNullAt(i)) Literal(null, vt)
+            else asNullable(Literal(vals.get(i, vt), vt))
+          Seq(k, v)
+        }
+        CreateMap(children, useStringTypeWhenEmpty = false)
+      case StructType(fields) =>
+        val row = value.asInstanceOf[InternalRow]
+        val children = fields.zipWithIndex.flatMap { case (f, i) =>
+          val v =
+            if (row.isNullAt(i)) Literal(null, f.dataType)
+            else asNullable(Literal(row.get(i, f.dataType), f.dataType))
+          Seq(Literal(f.name), v)
+        }
+        CreateNamedStruct(children.toSeq)

Review Comment:
   [P2] Preserve scalar/batch cardinality for reconstructed structs
   
   With normal constant folding, `SELECT id, named_struct('a', 1) FROM t` now 
reaches native `CreateNamedStruct` instead of retaining Spark projection 
fallback. For all-scalar children, its evaluator calls 
`ColumnarValue::values_to_arrays` and returns a one-row `StructArray`, 
regardless of the input batch size. With three rows in one Parquet batch, the 
query fails with `Array length 1 does not match expected length 3`; arrays 
containing such structs similarly fail the `make_array` row-count check. I 
reproduced this on Spark 3.5.9 and 4.1.3, while the exact base literal 
serializer returns all three rows correctly. Please preserve scalar 
semantics/broadcast to the input batch size, or retain fallback for these 
literals until the native constructor supports them.



##########
spark/src/main/scala/org/apache/comet/serde/literals.scala:
##########
@@ -215,4 +225,82 @@ object CometLiteral extends CometExpressionSerde[Literal] 
with Logging {
     }
     listLiteralBuilder
   }
+
+  /**
+   * True when a non-null Literal of this type is not encodable in the native 
`Literal` proto and
+   * `convert` should try to rebuild it from primitive-typed Literals. The 
native proto today
+   * carries scalars and nested `ListLiteral`s (arrays of arrays / arrays of 
scalars). It does not
+   * carry Map or Struct values, so any Literal whose type contains a 
`MapType` or a `StructType`
+   * needs to be expanded before serialization.
+   */
+  private def needsExpansion(dataType: DataType): Boolean = dataType match {
+    case _: MapType | _: StructType => true
+    case ArrayType(et, _) => needsExpansion(et)
+    case _ => false
+  }
+
+  /**
+   * True when the Literal is a non-null complex value that we can rebuild 
from primitive Literals
+   * via [[expandComplexLiteral]]. Empty top-level containers are excluded 
because a synthesized
+   * `Create[Array|Map]` with no children cannot recover the original element 
type.
+   */
+  private def canExpandComplexLiteral(expr: Literal): Boolean = {
+    val value = expr.value
+    if (value == null || !needsExpansion(expr.dataType)) return false
+    expr.dataType match {
+      case _: ArrayType => value.asInstanceOf[ArrayData].numElements() > 0
+      case _: MapType => value.asInstanceOf[MapData].numElements() > 0
+      case _: StructType => true
+      case _ => false
+    }
+  }
+
+  /**
+   * Rebuild a folded complex Literal as an equivalent tree of `CreateArray` / 
`CreateMap` /
+   * `CreateNamedStruct` over primitive-typed Literals. Callers must gate on
+   * [[canExpandComplexLiteral]] so `dataType` is one of the three complex 
cases and top-level
+   * containers are non-empty.
+   */
+  private def expandComplexLiteral(value: Any, dataType: DataType): Expression 
=
+    dataType match {
+      case ArrayType(et, _) =>
+        val arr = value.asInstanceOf[ArrayData]
+        val elems = (0 until arr.numElements()).map { i =>
+          if (arr.isNullAt(i)) Literal(null, et)
+          else asNullable(Literal(arr.get(i, et), et))
+        }
+        CreateArray(elems, useStringTypeWhenEmpty = false)
+      case MapType(kt, vt, _) =>
+        val mapData = value.asInstanceOf[MapData]
+        val keys = mapData.keyArray()
+        val vals = mapData.valueArray()
+        val children = (0 until keys.numElements()).flatMap { i =>
+          val k = if (keys.isNullAt(i)) Literal(null, kt) else 
Literal(keys.get(i, kt), kt)
+          val v =
+            if (vals.isNullAt(i)) Literal(null, vt)
+            else asNullable(Literal(vals.get(i, vt), vt))
+          Seq(k, v)
+        }
+        CreateMap(children, useStringTypeWhenEmpty = false)
+      case StructType(fields) =>
+        val row = value.asInstanceOf[InternalRow]
+        val children = fields.zipWithIndex.flatMap { case (f, i) =>
+          val v =
+            if (row.isNullAt(i)) Literal(null, f.dataType)
+            else asNullable(Literal(row.get(i, f.dataType), f.dataType))

Review Comment:
   [P2] Preserve struct-field nullability in the native representation
   
   `KnownNullable` is discarded by `CometKnownNullable.convert`, and native 
`CreateNamedStruct::fields` infers each field's nullability from its serialized 
child. With normal folding, `array(named_struct('a', CAST(NULL AS INT)), 
named_struct('a', 1))` is a single literal with a unified nullable struct type, 
so the outer `CometCreateArray` type guard passes. Recursive expansion then 
produces native struct fields with `nullable=true` and `nullable=false`. 
DataFusion 54.1 preserves those per-input flags during struct coercion, and 
Arrow panics in `MutableArrayData` with `Arrays with inconsistent types`. This 
reproduces on Spark 3.5.9 and 4.1.3 and the base serializer safely falls back. 
Please carry/normalize the field nullability on the native wire, or decline 
expansion when it cannot be preserved; the Catalyst-only wrapper does not 
prevent this panic.



-- 
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