bhollis-dbx commented on code in PR #59283:
URL: https://github.com/apache/spark/pull/59283#discussion_r4239483002
##########
sql/core/src/test/scala/org/apache/spark/sql/ColumnExpressionSuite.scala:
##########
@@ -445,6 +446,141 @@ class ColumnExpressionSuite extends SharedSparkSession {
SQLConf.CODEGEN_FACTORY_MODE.key -> "CODEGEN_ONLY")(f)
}
+ test("withField should evaluate a nondeterministic struct expression once") {
+ onEachEvalPath {
+ val counter = new java.util.concurrent.atomic.AtomicLong()
+ val nextStruct = udf(() => {
+ val value = counter.getAndIncrement()
+ (value, value)
+ }).asNondeterministic()
+ val structExpr = nextStruct()
+ val df = spark.range(0, 10, 1, 1)
+ df.select(structExpr.withField("copy", structExpr)).collect().foreach {
row =>
+ val result = row.getStruct(0)
+ val copy = result.getStruct(2)
+ assert(result.getLong(0) == result.getLong(1))
+ assert(copy.getLong(0) == copy.getLong(1))
+ assert(result.getLong(0) != copy.getLong(0))
+ }
+ }
+ }
+
+ test("withField should evaluate a reused nondeterministic value separately")
{
+ onEachEvalPath {
+ val counter = new java.util.concurrent.atomic.AtomicLong()
+ val nextStruct = udf(() => {
+ val value = counter.getAndIncrement()
+ (value, value)
+ }).asNondeterministic()
+ val structExpr = nextStruct().expr
+ val updated = Column(UpdateFields(structExpr,
+ Seq(WithField("copy", GetStructField(structExpr, 0)))))
+ val df = spark.range(0, 10, 1, 1)
+ df.select(updated).collect().foreach { row =>
+ val result = row.getStruct(0)
+ assert(result.getLong(0) == result.getLong(1))
+ assert(result.getLong(0) != result.getLong(2))
+ }
+ }
+ }
+
+ test("withField should evaluate a reused nested update separately") {
+ onEachEvalPath {
+ val counter = new java.util.concurrent.atomic.AtomicLong()
+ val nextStruct = udf(() => {
+ val value = counter.getAndIncrement()
+ (value, value)
+ }).asNondeterministic()
+ val structExpr = nextStruct().expr
+ val nestedUpdate = UpdateFields(structExpr, Seq(WithField("extra",
Literal(1))))
+ val updated = Column(UpdateFields(structExpr,
+ Seq(WithField("copy", GetStructField(nestedUpdate, 0)))))
+ val df = spark.range(0, 10, 1, 1)
+ df.select(updated).collect().foreach { row =>
+ val result = row.getStruct(0)
+ assert(result.getLong(0) == result.getLong(1))
+ assert(result.getLong(0) != result.getLong(2))
+ }
+ }
+ }
+
+ test("withField should reuse an updated struct for a nested path") {
Review Comment:
Added DataFrame tests through analysis and optimization for unresolved
nested chains, later drops and overwrites, and input-dependent nondeterministic
structs.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/complexTypeCreator.scala:
##########
@@ -806,35 +969,129 @@ case class UpdateFields(structExpr: Expression,
fieldOps: Seq[StructFieldsOperat
override def prettyName: String = "update_fields"
- private lazy val newFieldExprs: Seq[(StructField, Expression)] = {
- def getFieldExpr(i: Int): Expression = structExpr match {
- case c: CreateNamedStruct => c.valExprs(i)
- case _ => GetStructField(structExpr, i)
- }
- val fieldsWithIndex =
structExpr.dataType.asInstanceOf[StructType].fields.zipWithIndex
- val existingFieldExprs: Seq[(StructField, Expression)] =
- fieldsWithIndex.map { case (field, i) => (field, getFieldExpr(i))
}.toImmutableArraySeq
- fieldOps.foldLeft(existingFieldExprs)((exprs, op) => op(exprs))
- }
+ private lazy val newFieldSources: Seq[(StructField,
UpdateFields.FieldSource)] =
+ UpdateFields.fieldSources(structExpr.dataType.asInstanceOf[StructType],
fieldOps)
- private lazy val newFields: Seq[StructField] = newFieldExprs.map(_._1)
+ private lazy val newFields: Seq[StructField] = newFieldSources.map(_._1)
- lazy val newExprs: Seq[Expression] = newFieldExprs.map(_._2)
+ /** Returns the expression for one output field without constructing the
complete struct. */
+ def newExpr(ordinal: Int): Expression = newFieldSources(ordinal)._2 match {
+ case UpdateFields.Original(sourceOrdinal) => getFieldExpr(sourceOrdinal)
+ case UpdateFields.Updated(expr) => expr
+ }
- lazy val evalExpr: Expression = {
- val createNamedStructExpr = CreateNamedStruct(newFieldExprs.flatMap {
- case (field, expr) => Seq(Literal(field.name), expr)
- })
+ /**
+ * Returns an input field expression and marks generated reads so they can
share `structExpr`
+ * without changing equivalent expressions supplied to `withField`.
+ */
+ private def getFieldExpr(ordinal: Int): Expression = structExpr match {
+ case c: CreateNamedStruct => c.valExprs(ordinal)
+ case _ => UpdateFields.markGeneratedStructRead(
+ GetStructField(structExpr, ordinal), generatedStructReadOwner)
+ }
- if (structExpr.nullable) {
- If(IsNull(structExpr), Literal(null, dataType), createNamedStructExpr)
- } else {
- createNamedStructExpr
+ lazy val evalExpr: Expression = With(structExpr) { case Seq(structRef) =>
+ // Read fields used to rebuild the input struct from structRef so
structExpr is evaluated once.
+ // Do not rewrite expressions passed to withField; they must be evaluated
independently.
Review Comment:
Removed the comment along with the identity/tag-based rewriting machinery.
Nested updates are now represented explicitly.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/complexTypeCreator.scala:
##########
@@ -764,12 +764,175 @@ case class DropField(name: String) extends
StructFieldsOperation with LeafLike[E
values.filterNot { case (field, _) => resolver(field.name, name) }
}
+/**
+ * Builds a struct from fields copied from `structExpr` and values computed by
`updatedValues`.
+ *
+ * `outputOrdinals` describes each output position. A nonnegative entry copies
that field from
+ * `structExpr`; a negative entry evaluates the next expression in
`updatedValues`. Storing copied
+ * fields as ordinals avoids adding a `GetStructField` child for every copied
field. Row construction
+ * still visits every output field.
+ *
+ * For example, to transform `(a, b, c)` into `(a, newB, c, newD)`,
`outputOrdinals` is
+ * `Seq(0, -1, 2, -1)` and `updatedValues` is `Seq(newB, newD)`. The first and
third output fields
+ * copy source fields 0 and 2. The negative entries use `newB` and `newD`, in
that order.
+ *
+ * If a nullable `structExpr` evaluates to null, the result is null and
`updatedValues` are not
+ * evaluated.
+ */
+private case class UpdateFieldsExpression(
+ structExpr: Expression,
+ updatedValues: Seq[Expression],
+ outputOrdinals: Seq[Int],
+ outputFields: Seq[StructField]) extends Expression {
+
+ override def children: Seq[Expression] = structExpr +: updatedValues
+
+ override def nullable: Boolean = structExpr.nullable
+
+ override lazy val dataType: StructType = {
+ val sourceFields = structExpr.dataType.asInstanceOf[StructType]
+ var updatedIndex = 0
+ StructType(outputOrdinals.zipWithIndex.map { case (sourceOrdinal, index) =>
+ if (sourceOrdinal >= 0) {
+ sourceFields(sourceOrdinal)
+ } else {
+ val updatedValue = updatedValues(updatedIndex)
+ updatedIndex += 1
+ outputFields(index).copy(
+ dataType = updatedValue.dataType,
+ nullable = updatedValue.nullable)
+ }
+ })
+ }
+
+ override def eval(input: InternalRow): Any = {
+ val needsSource = nullable || outputOrdinals.exists(_ >= 0)
+ val sourceRow = if (needsSource) {
+ structExpr.eval(input).asInstanceOf[InternalRow]
+ } else {
+ null
+ }
+ if (sourceRow == null && nullable) {
+ null
+ } else {
+ val values = new Array[Any](outputOrdinals.length)
+ var updatedIndex = 0
+ var i = 0
+ while (i < outputOrdinals.length) {
+ val sourceOrdinal = outputOrdinals(i)
Review Comment:
Converted per-row indexed state to arrays and cached needsSource. The
evaluation loop no longer indexes Scala Lists or rescans source requirements.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/complexTypeCreator.scala:
##########
@@ -806,35 +969,129 @@ case class UpdateFields(structExpr: Expression,
fieldOps: Seq[StructFieldsOperat
override def prettyName: String = "update_fields"
- private lazy val newFieldExprs: Seq[(StructField, Expression)] = {
- def getFieldExpr(i: Int): Expression = structExpr match {
- case c: CreateNamedStruct => c.valExprs(i)
- case _ => GetStructField(structExpr, i)
- }
- val fieldsWithIndex =
structExpr.dataType.asInstanceOf[StructType].fields.zipWithIndex
- val existingFieldExprs: Seq[(StructField, Expression)] =
- fieldsWithIndex.map { case (field, i) => (field, getFieldExpr(i))
}.toImmutableArraySeq
- fieldOps.foldLeft(existingFieldExprs)((exprs, op) => op(exprs))
- }
+ private lazy val newFieldSources: Seq[(StructField,
UpdateFields.FieldSource)] =
+ UpdateFields.fieldSources(structExpr.dataType.asInstanceOf[StructType],
fieldOps)
- private lazy val newFields: Seq[StructField] = newFieldExprs.map(_._1)
+ private lazy val newFields: Seq[StructField] = newFieldSources.map(_._1)
- lazy val newExprs: Seq[Expression] = newFieldExprs.map(_._2)
+ /** Returns the expression for one output field without constructing the
complete struct. */
+ def newExpr(ordinal: Int): Expression = newFieldSources(ordinal)._2 match {
+ case UpdateFields.Original(sourceOrdinal) => getFieldExpr(sourceOrdinal)
+ case UpdateFields.Updated(expr) => expr
+ }
- lazy val evalExpr: Expression = {
- val createNamedStructExpr = CreateNamedStruct(newFieldExprs.flatMap {
- case (field, expr) => Seq(Literal(field.name), expr)
- })
+ /**
+ * Returns an input field expression and marks generated reads so they can
share `structExpr`
+ * without changing equivalent expressions supplied to `withField`.
+ */
+ private def getFieldExpr(ordinal: Int): Expression = structExpr match {
+ case c: CreateNamedStruct => c.valExprs(ordinal)
+ case _ => UpdateFields.markGeneratedStructRead(
Review Comment:
Removed the generated-read tags and simplification guards. Ordinary field
extraction simplifies again, with a regression covering an unused throwing
update.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/complexTypeCreator.scala:
##########
@@ -764,12 +764,175 @@ case class DropField(name: String) extends
StructFieldsOperation with LeafLike[E
values.filterNot { case (field, _) => resolver(field.name, name) }
}
+/**
+ * Builds a struct from fields copied from `structExpr` and values computed by
`updatedValues`.
+ *
+ * `outputOrdinals` describes each output position. A nonnegative entry copies
that field from
+ * `structExpr`; a negative entry evaluates the next expression in
`updatedValues`. Storing copied
+ * fields as ordinals avoids adding a `GetStructField` child for every copied
field. Row construction
+ * still visits every output field.
+ *
+ * For example, to transform `(a, b, c)` into `(a, newB, c, newD)`,
`outputOrdinals` is
+ * `Seq(0, -1, 2, -1)` and `updatedValues` is `Seq(newB, newD)`. The first and
third output fields
+ * copy source fields 0 and 2. The negative entries use `newB` and `newD`, in
that order.
+ *
+ * If a nullable `structExpr` evaluates to null, the result is null and
`updatedValues` are not
+ * evaluated.
+ */
+private case class UpdateFieldsExpression(
+ structExpr: Expression,
+ updatedValues: Seq[Expression],
+ outputOrdinals: Seq[Int],
+ outputFields: Seq[StructField]) extends Expression {
+
+ override def children: Seq[Expression] = structExpr +: updatedValues
+
+ override def nullable: Boolean = structExpr.nullable
+
+ override lazy val dataType: StructType = {
+ val sourceFields = structExpr.dataType.asInstanceOf[StructType]
+ var updatedIndex = 0
+ StructType(outputOrdinals.zipWithIndex.map { case (sourceOrdinal, index) =>
+ if (sourceOrdinal >= 0) {
+ sourceFields(sourceOrdinal)
+ } else {
+ val updatedValue = updatedValues(updatedIndex)
+ updatedIndex += 1
+ outputFields(index).copy(
+ dataType = updatedValue.dataType,
+ nullable = updatedValue.nullable)
+ }
+ })
+ }
+
+ override def eval(input: InternalRow): Any = {
+ val needsSource = nullable || outputOrdinals.exists(_ >= 0)
+ val sourceRow = if (needsSource) {
+ structExpr.eval(input).asInstanceOf[InternalRow]
+ } else {
+ null
+ }
+ if (sourceRow == null && nullable) {
+ null
+ } else {
+ val values = new Array[Any](outputOrdinals.length)
+ var updatedIndex = 0
+ var i = 0
+ while (i < outputOrdinals.length) {
+ val sourceOrdinal = outputOrdinals(i)
+ if (sourceOrdinal >= 0) {
+ values(i) = if (sourceRow == null) {
+ null
+ } else {
+ sourceRow.get(sourceOrdinal, dataType(i).dataType)
+ }
+ } else {
+ values(i) = updatedValues(updatedIndex).eval(input)
+ updatedIndex += 1
+ }
+ i += 1
+ }
+ new GenericInternalRow(values)
+ }
+ }
+
+ override protected def withNewChildrenInternal(
+ newChildren: IndexedSeq[Expression]): UpdateFieldsExpression =
+ copy(structExpr = newChildren.head, updatedValues = newChildren.tail)
+
+ override protected def doGenCode(ctx: CodegenContext, ev: ExprCode):
ExprCode = {
+ val rowClass = classOf[GenericInternalRow].getName
+ val values = ctx.freshName("values")
+ val needsSource = nullable || outputOrdinals.exists(_ >= 0)
+ val sourceCode = if (needsSource) Some(structExpr.genCode(ctx)) else None
+ val sourceRow = ctx.freshName("sourceRow")
+
+ val copyValuesCode = if (outputOrdinals.exists(_ >= 0)) {
+ val index = ctx.freshName("i")
+ val sourceOrdinal = ctx.freshName("sourceOrdinal")
+ val sourceOrdinals = ctx.addReferenceObj("sourceOrdinals",
outputOrdinals.toArray, "int[]")
+ val fieldTypes = ctx.addReferenceObj(
+ "fieldTypes",
+ dataType.fields.map(_.dataType).toArray,
+ s"${classOf[DataType].getName}[]")
+ s"""
+ |for (int $index = 0; $index < ${outputOrdinals.length}; $index++) {
+ | int $sourceOrdinal = $sourceOrdinals[$index];
+ | if ($sourceOrdinal >= 0) {
+ | $values[$index] = $sourceRow == null ? null :
+ | $sourceRow.get($sourceOrdinal, $fieldTypes[$index]);
Review Comment:
Generated copies now use CodeGenerator.getValue, grouped by field type.
Added vectorized Parquet regressions for unchanged TIME, GEOMETRY, and
GEOGRAPHY fields.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/UpdateFields.scala:
##########
@@ -68,7 +68,9 @@ object OptimizeUpdateFields extends Rule[LogicalPlan] {
val newWithFields = newNames.map(n => WithField(nameMap(n),
newValues(n)))
UpdateFields(structExpr, newWithFields.toSeq)
- case UpdateFields(UpdateFields(struct, fieldOps1), fieldOps2) =>
+ // Flattening would separate a nested field read from the struct it reads.
+ case UpdateFields(UpdateFields(struct, fieldOps1), fieldOps2)
+ if !(fieldOps1 ++
fieldOps2).exists(_.exists(UpdateFields.isGeneratedStructRead)) =>
Review Comment:
Removed the guard and restored chain flattening. Nested operations compose
before resolution. Added full-pipeline tests for chained nested withField and
dropFields calls on unresolved columns.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/complexTypeCreator.scala:
##########
@@ -806,35 +969,129 @@ case class UpdateFields(structExpr: Expression,
fieldOps: Seq[StructFieldsOperat
override def prettyName: String = "update_fields"
- private lazy val newFieldExprs: Seq[(StructField, Expression)] = {
- def getFieldExpr(i: Int): Expression = structExpr match {
- case c: CreateNamedStruct => c.valExprs(i)
- case _ => GetStructField(structExpr, i)
- }
- val fieldsWithIndex =
structExpr.dataType.asInstanceOf[StructType].fields.zipWithIndex
- val existingFieldExprs: Seq[(StructField, Expression)] =
- fieldsWithIndex.map { case (field, i) => (field, getFieldExpr(i))
}.toImmutableArraySeq
- fieldOps.foldLeft(existingFieldExprs)((exprs, op) => op(exprs))
- }
+ private lazy val newFieldSources: Seq[(StructField,
UpdateFields.FieldSource)] =
+ UpdateFields.fieldSources(structExpr.dataType.asInstanceOf[StructType],
fieldOps)
- private lazy val newFields: Seq[StructField] = newFieldExprs.map(_._1)
+ private lazy val newFields: Seq[StructField] = newFieldSources.map(_._1)
- lazy val newExprs: Seq[Expression] = newFieldExprs.map(_._2)
+ /** Returns the expression for one output field without constructing the
complete struct. */
+ def newExpr(ordinal: Int): Expression = newFieldSources(ordinal)._2 match {
+ case UpdateFields.Original(sourceOrdinal) => getFieldExpr(sourceOrdinal)
+ case UpdateFields.Updated(expr) => expr
+ }
- lazy val evalExpr: Expression = {
- val createNamedStructExpr = CreateNamedStruct(newFieldExprs.flatMap {
- case (field, expr) => Seq(Literal(field.name), expr)
- })
+ /**
+ * Returns an input field expression and marks generated reads so they can
share `structExpr`
+ * without changing equivalent expressions supplied to `withField`.
+ */
+ private def getFieldExpr(ordinal: Int): Expression = structExpr match {
+ case c: CreateNamedStruct => c.valExprs(ordinal)
+ case _ => UpdateFields.markGeneratedStructRead(
+ GetStructField(structExpr, ordinal), generatedStructReadOwner)
+ }
- if (structExpr.nullable) {
- If(IsNull(structExpr), Literal(null, dataType), createNamedStructExpr)
- } else {
- createNamedStructExpr
+ lazy val evalExpr: Expression = With(structExpr) { case Seq(structRef) =>
+ // Read fields used to rebuild the input struct from structRef so
structExpr is evaluated once.
+ // Do not rewrite expressions passed to withField; they must be evaluated
independently.
+ def replaceStruct(expr: Expression): Expression = expr.transformDown {
+ case field @ GetStructField(child, _, _)
+ if UpdateFields.isGeneratedStructRead(field,
generatedStructReadOwner) &&
+ child.eq(structExpr) =>
+ field.copy(child = structRef)
}
+
+ val updatedValues = ArrayBuffer.empty[Expression]
+ val outputOrdinals = newFieldSources.map { case (_, source) =>
+ source match {
+ case UpdateFields.Original(ordinal) => structExpr match {
+ case namedStruct: CreateNamedStruct =>
+ updatedValues += namedStruct.valExprs(ordinal)
+ -1
+ case _ => ordinal
+ }
+ case UpdateFields.Updated(expr) =>
+ updatedValues += replaceStruct(expr)
+ -1
+ }
+ }
+ UpdateFieldsExpression(structRef, updatedValues.toSeq, outputOrdinals,
newFields)
}
}
object UpdateFields {
+ /** Describes where an output field gets its value without creating an
expression for a copy. */
+ private sealed trait FieldSource
+
+ /** Copies the field at `ordinal` from the input struct. */
+ private case class Original(ordinal: Int) extends FieldSource
+
+ /** Computes the field from an expression supplied to `withField`. */
+ private case class Updated(expr: Expression) extends FieldSource
+
+ /**
+ * Applies the requested field operations while retaining whether each
output field is copied or
+ * computed. This provenance lets evaluation copy unchanged fields by
ordinal and lets extraction
+ * return one field without constructing the complete struct.
+ */
+ private def fieldSources(
+ structType: StructType,
+ fieldOps: Seq[StructFieldsOperation]): Seq[(StructField, FieldSource)] =
{
+ val fields: Seq[(StructField, FieldSource)] =
structType.fields.zipWithIndex.map {
+ case (field, ordinal) =>
+ field -> Original(ordinal)
+ }.toImmutableArraySeq
+ fieldOps.foldLeft(fields) {
+ case (current, op: WithField) =>
+ val updated = StructField(op.name, op.valExpr.dataType,
op.valExpr.nullable) ->
+ Updated(op.valExpr)
+ val result = ArrayBuffer.empty[(StructField, FieldSource)]
+ var hasMatch = false
+ current.foreach { fieldSource =>
+ if (op.resolver(fieldSource._1.name, op.name)) {
+ hasMatch = true
+ result += updated
+ } else {
+ result += fieldSource
+ }
+ }
+ if (!hasMatch) result += updated
+ result.toSeq
+ case (current, op: DropField) =>
+ current.filterNot { case (field, _) => op.resolver(field.name,
op.name) }
+ case (_, op) =>
Review Comment:
Removed the unused StructFieldsOperation.apply implementations and updated
the documentation. Name matching now lives in fieldSources.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/complexTypeCreator.scala:
##########
@@ -806,35 +969,129 @@ case class UpdateFields(structExpr: Expression,
fieldOps: Seq[StructFieldsOperat
override def prettyName: String = "update_fields"
- private lazy val newFieldExprs: Seq[(StructField, Expression)] = {
- def getFieldExpr(i: Int): Expression = structExpr match {
- case c: CreateNamedStruct => c.valExprs(i)
- case _ => GetStructField(structExpr, i)
- }
- val fieldsWithIndex =
structExpr.dataType.asInstanceOf[StructType].fields.zipWithIndex
- val existingFieldExprs: Seq[(StructField, Expression)] =
- fieldsWithIndex.map { case (field, i) => (field, getFieldExpr(i))
}.toImmutableArraySeq
- fieldOps.foldLeft(existingFieldExprs)((exprs, op) => op(exprs))
- }
+ private lazy val newFieldSources: Seq[(StructField,
UpdateFields.FieldSource)] =
+ UpdateFields.fieldSources(structExpr.dataType.asInstanceOf[StructType],
fieldOps)
- private lazy val newFields: Seq[StructField] = newFieldExprs.map(_._1)
+ private lazy val newFields: Seq[StructField] = newFieldSources.map(_._1)
- lazy val newExprs: Seq[Expression] = newFieldExprs.map(_._2)
+ /** Returns the expression for one output field without constructing the
complete struct. */
+ def newExpr(ordinal: Int): Expression = newFieldSources(ordinal)._2 match {
+ case UpdateFields.Original(sourceOrdinal) => getFieldExpr(sourceOrdinal)
+ case UpdateFields.Updated(expr) => expr
+ }
- lazy val evalExpr: Expression = {
- val createNamedStructExpr = CreateNamedStruct(newFieldExprs.flatMap {
- case (field, expr) => Seq(Literal(field.name), expr)
- })
+ /**
+ * Returns an input field expression and marks generated reads so they can
share `structExpr`
+ * without changing equivalent expressions supplied to `withField`.
+ */
+ private def getFieldExpr(ordinal: Int): Expression = structExpr match {
+ case c: CreateNamedStruct => c.valExprs(ordinal)
+ case _ => UpdateFields.markGeneratedStructRead(
+ GetStructField(structExpr, ordinal), generatedStructReadOwner)
+ }
- if (structExpr.nullable) {
- If(IsNull(structExpr), Literal(null, dataType), createNamedStructExpr)
- } else {
- createNamedStructExpr
+ lazy val evalExpr: Expression = With(structExpr) { case Seq(structRef) =>
+ // Read fields used to rebuild the input struct from structRef so
structExpr is evaluated once.
+ // Do not rewrite expressions passed to withField; they must be evaluated
independently.
+ def replaceStruct(expr: Expression): Expression = expr.transformDown {
+ case field @ GetStructField(child, _, _)
+ if UpdateFields.isGeneratedStructRead(field,
generatedStructReadOwner) &&
+ child.eq(structExpr) =>
Review Comment:
Replaced identity-based sharing with UpdateNestedField, which applies nested
operations to the current field value without duplicating the parent input.
Added unresolved-column and input-dependent nondeterministic regressions.
--
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]