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


##########
spark/src/main/scala/org/apache/comet/serde/literals.scala:
##########
@@ -215,4 +225,168 @@ object CometLiteral extends CometExpressionSerde[Literal] 
with Logging {
     }
     listLiteralBuilder
   }
+
+  /**
+   * Rebuild a folded complex Literal as an equivalent tree of `CreateArray` / 
`CreateMap` over
+   * primitive-typed Literals, or `None` when the shape cannot be rebuilt. 
`getSupportLevel`
+   * probes with this same function and reports `Compatible` when it returns a 
`Some`, so
+   * admission and expansion cannot diverge. The native `Literal` proto 
carries scalars and nested
+   * `ListLiteral`s but no map values, so a Literal whose type contains a 
`MapType` has to be
+   * expanded before serialization. Teaching the proto to transport maps 
directly would remove the
+   * need for this rewrite and for the declines below:
+   * https://github.com/apache/datafusion-comet/issues/1937
+   *
+   * The rebuilt tree is the tree Spark itself had before `ConstantFolding` 
collapsed it, down to
+   * every container's declared nullability (see [[withNullability]]). That 
equivalence is the
+   * safety property this rewrite rests on: whatever the rebuilt expression 
does natively is what
+   * the same query already does with `ConstantFolding` disabled, so expansion 
cannot introduce a
+   * folding-only behaviour difference. A non-nullable-value map into 
`map_entries` is handled by
+   * the native planner widening its argument rather than declined here.
+   *
+   * Map key semantics, however, are declined here at admission (see 
[[mapKeyTypesExpandable]])
+   * rather than left to consumers. A map lookup gates itself on 
[[MapKeySupport]], but not every
+   * native map consumer does: `map_contains_key` lowers to 
`array_contains(map_keys(...), key)`,
+   * and neither kernel checks the key type. A map that is only the value of 
another map is also
+   * opaque once rebuilt, because `CometCreateMap` hands the whole `CreateMap` 
to the JVM codegen
+   * dispatcher, so a nested unsupported key type never revisits this serde. 
Declining any folded
+   * literal that (recursively) contains an unsupported or non-orderable map 
key type keeps all of
+   * those paths on Spark.
+   *
+   * Declined shapes:
+   *   - Null values and empty top-level containers: a synthesized `Create*` 
with no children
+   *     cannot recover the original element type. Empty `ArrayType` literals 
still serialize via
+   *     `makeListLiteral`, which keeps the type.
+   *   - Any map key type native map kernels cannot reproduce Spark equality 
for (floating-point,
+   *     collated string, complex), or whose interpreted ordering is undefined
+   *     (`CalendarIntervalType`), at any nesting level. See 
[[mapKeyTypesExpandable]].
+   *   - Arrays whose elements are structs, because [[needsExpansion]] does 
not walk into a
+   *     `StructType`. Native `CreateNamedStruct` builds a 1-row `StructArray` 
whenever all of its
+   *     children are scalars (`values_to_arrays`), which collides with the 
surrounding batch's
+   *     row count, and its proto message carries no type, so Spark's declared 
field nullability
+   *     cannot survive the wire either way. A struct that is only a map value 
is safe:
+   *     `CometCreateMap` hands the whole rebuilt `CreateMap` to the JVM 
codegen dispatcher, so
+   *     Spark's own code builds the struct.
+   *   - Folded maps with duplicate keys, see [[hasDuplicateMapKeys]].
+   */
+  private def expandComplexLiteral(expr: Literal): Option[Expression] = {
+    if (expr.value == null || !mapKeyTypesExpandable(expr.dataType)) return 
None
+    expr.dataType match {
+      case ArrayType(et, containsNull) if needsExpansion(et) =>
+        val arr = expr.value.asInstanceOf[ArrayData]
+        if (arr.numElements() == 0) {
+          None
+        } else {
+          val elements = (0 until arr.numElements())
+            .map(i => withNullability(literalAt(arr, i, et), containsNull))
+          Some(CreateArray(elements, useStringTypeWhenEmpty = false))
+        }
+      case MapType(kt, vt, valueContainsNull) =>
+        val mapData = expr.value.asInstanceOf[MapData]
+        val keys = mapData.keyArray()
+        if (mapData.numElements() == 0 || hasDuplicateMapKeys(keys, kt)) {
+          None
+        } else {
+          val values = mapData.valueArray()
+          val children = (0 until keys.numElements()).flatMap(i =>
+            Seq(
+              literalAt(keys, i, kt),
+              withNullability(literalAt(values, i, vt), valueContainsNull)))
+          Some(CreateMap(children, useStringTypeWhenEmpty = false))

Review Comment:
   [P2] Retain a safe execution path for large folded map literals
   
   Rebuilding every stored entry as constructor children newly sends large 
folded maps through the dispatcher's nested generated classes. With normal 
folding and a Parquet `id INT` column containing `1, 2, 3`:
   
   ```sql
   SELECT id, element_at(
     map_from_arrays(sequence(1, 100000), sequence(1, 100000)),
     id) AS v
   FROM t
   ```
   
   Spark returns `(1,1), (2,2), (3,3)`, but this head selects `CometProject` 
and fails with `IllegalAccessError`: 
`SpecificCometBatchKernel$NestedClass_8.CreateMap_0$` accesses the protected 
`CometBatchKernel.references` field. The generated helper uses that field for 
its map builder. The schema-field-count admission check does not constrain map 
entry count, and there is no execution-time fallback.
   
   This reproduced twice on Spark 4.0.4/JDK 17. The exact-base literal 
serializer and dispatcher-off controls both return the correct rows; a 
10,000-entry native control passes. Please retain fallback for expansions the 
dispatcher cannot execute, preserve the compact literal representation, or fix 
generated-helper reference access before admitting them.



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