cloud-fan commented on code in PR #57827:
URL: https://github.com/apache/spark/pull/57827#discussion_r3936021194


##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/variant/variantExpressions.scala:
##########
@@ -1851,6 +1905,82 @@ object VariantExplode {
       case _ => Nil
     }
   }
+
+  private case class ExplodeEntry(path: String, pos: Int, key: UTF8String, 
value: Variant)
+
+  def variantExplodeRecursive(
+      input: VariantVal,
+      isNull: Boolean): Iterable[InternalRow] = {
+    if (isNull) {
+      return Iterable.empty
+    }
+
+    new Iterable[InternalRow] {
+      override def iterator: Iterator[InternalRow] = {

Review Comment:
   **Non-blocking (P2):** For interpreted variant_explode_outer, GenerateExec 
first probes outputRows.iterator.isEmpty and then requests another iterator for 
consumption. This Iterable rebuilds and repopulates the complete root stack on 
each request, so every non-empty row repeats O(number of direct children) path 
creation and allocation. Could this return a one-shot Iterator or IterableOnce 
whose initialized stack is shared by the probe and consumption?



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/variant/variantExpressions.scala:
##########
@@ -1851,6 +1905,82 @@ object VariantExplode {
       case _ => Nil
     }
   }
+
+  private case class ExplodeEntry(path: String, pos: Int, key: UTF8String, 
value: Variant)
+
+  def variantExplodeRecursive(
+      input: VariantVal,
+      isNull: Boolean): Iterable[InternalRow] = {
+    if (isNull) {
+      return Iterable.empty
+    }
+
+    new Iterable[InternalRow] {
+      override def iterator: Iterator[InternalRow] = {
+        val stack = new ArrayDeque[ExplodeEntry]()
+        pushChildren(new Variant(input.getValue, input.getMetadata), "$", 
stack)
+        new Iterator[InternalRow] {
+          override def hasNext: Boolean = !stack.isEmpty
+
+          override def next(): InternalRow = {
+            val entry = stack.pop()
+            pushChildren(entry.value, entry.path, stack)
+            InternalRow(
+              UTF8String.fromString(entry.path),
+              entry.pos,
+              entry.key,
+              new VariantVal(entry.value.getValue, entry.value.getMetadata))
+          }
+        }
+      }
+    }
+  }
+
+  private def pushChildren(
+      v: Variant,
+      parentPath: String,
+      stack: ArrayDeque[ExplodeEntry]): Unit = {
+    v.getType match {
+      case Type.OBJECT =>
+        for (i <- v.objectSize() - 1 to 0 by -1) {
+          val field = v.getFieldAtIndex(i)
+          stack.push(ExplodeEntry(
+            appendObjectPath(parentPath, field.key),
+            i,
+            UTF8String.fromString(field.key),
+            field.value))
+        }
+      case Type.ARRAY =>
+        for (i <- v.arraySize() - 1 to 0 by -1) {
+          stack.push(ExplodeEntry(
+            s"$parentPath[$i]",
+            i,
+            null,
+            v.getElementAtIndex(i)))
+        }
+      case _ =>
+    }
+  }
+
+  // Appends `key` to `parentPath`: dot notation for dot-safe keys, else 
`['...']` with `\` and
+  // `'` escaped.
+  private def appendObjectPath(parentPath: String, key: String): String = {
+    if (isDotSafeKey(key)) {
+      s"$parentPath.$key"
+    } else {
+      s"$parentPath['${key.replace("\\", "\\\\").replace("'", "\\'")}']"

Review Comment:
   **Blocking (P1):** The quoted-key renderer and VariantPathParser use 
different escape rules. Apostrophe paths are rejected, doubled backslashes 
address a different key, and control characters such as tabs remain raw even 
though this column is documented as JSONPath. Could we give appendObjectPath 
and VariantPathParser one reversible escape contract and add tests that feed 
these emitted paths back into variant_get?
   
   **Recommended change:** Use one reversible quoted-key escape contract for 
recursive path generation and VariantPathParser, and verify it through 
variant_get round trips.
   
   **Why this works:** Encode quotes, backslashes, and control characters in 
appendObjectPath with syntax that VariantPathParser recognizes and decodes back 
to the original object key.
   
   **Scope:** Catalyst Variant path rendering/parsing and focused VariantSuite 
round-trip coverage.
   
   **Compatibility:** Preserve existing simple dot and bracket paths while 
making every newly emitted quoted path consumable; account for callers that 
currently treat backslashes literally.
   
   **Risks:** Changing parser escape semantics could reinterpret existing 
manually supplied bracket paths containing literal backslashes.
   
   **Constraints:** Support the full valid Variant object-key domain, including 
both quote characters, backslashes, and controls. Keep the emitted path and 
variant_get consumer on the same representation contract.
   
   **Success:** For every valid object key, feeding a recursive explode row's 
path to variant_get retrieves that row's value without a parse error or key 
change.



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