This is an automated email from the ASF dual-hosted git repository.

voonhous pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git


The following commit(s) were added to refs/heads/master by this push:
     new 7b843ae2c35f refactor(spark): consolidate the vendored 3.x Avro serde 
forks into hudi-spark3-common (#19168)
7b843ae2c35f is described below

commit 7b843ae2c35fa00993a2a1b102c9ad7e1e116267
Author: Y Ethan Guo <[email protected]>
AuthorDate: Fri Jul 10 06:32:00 2026 -0700

    refactor(spark): consolidate the vendored 3.x Avro serde forks into 
hudi-spark3-common (#19168)
    
    * refactor(spark): consolidate the vendored 3.x Avro serde forks into 
hudi-spark3-common
    
    The vendored spark-avro AvroSerializer and AvroDeserializer are duplicated
    across hudi-spark3.3.x, hudi-spark3.4.x and hudi-spark3.5.x. The 3.3 and 3.4
    copies are byte-identical; the 3.5 copies differ only in how they import
    LegacyBehaviorPolicy. Move a single copy of each into hudi-spark3-common,
    which every 3.x version module already depends on, and delete the six
    duplicates.
    
    The one cross-version snag is the LegacyBehaviorPolicy enum: it is nested in
    SQLConf (org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy) on 
Spark
    3.3/3.4 but a top-level object 
(org.apache.spark.sql.internal.LegacyBehaviorPolicy)
    on Spark 3.5, so no single explicit import resolves on all three (verified 
by
    compiling against the 3.3.4/3.4.3/3.5.5 catalyst jars). The shared source
    imports both containers via wildcards; exactly one of them contributes the
    enum on any given version, so there is no ambiguity and the bodies stay
    byte-identical to the originals. The per-version 
HoodieSpark3_xAvro{Serializer,
    Deserializer} wrappers are unchanged, since the shared classes keep the same
    constructors they called.
    
    Consolidating also gives each file a unique repo path, so it re-enters the
    coverage denominator: the identically-pathed copies were dropped by
    report-path resolution and were invisible to Codecov.
    
    * refactor(spark): consolidate the vendored 4.x Avro serde forks into 
hudi-spark4-common
    
    The vendored spark-avro AvroSerializer and AvroDeserializer are duplicated
    across hudi-spark4.0.x, hudi-spark4.1.x and hudi-spark4.2.x.
    
    AvroSerializer: the 4.0/4.1/4.2 bodies are identical except for the private
    convenience constructor that reads AVRO_REBASE_MODE_IN_WRITE from SQLConf.
    On Spark 4.0 that ConfigEntry is typed as String (so the read wraps it in
    LegacyBehaviorPolicy.withName), while on 4.1+ it is already a
    LegacyBehaviorPolicy.Value; a single shared source cannot express both. 
Since
    the only callers are the per-version HoodieSpark4_xAvroSerializer wrappers,
    move that read into each wrapper (verbatim per version) and drop the
    convenience constructor, leaving one shared AvroSerializer in
    hudi-spark4-common that all three versions use.
    
    AvroDeserializer: 4.1 and 4.2 are identical apart from one comment word, so
    they collapse to a single shared copy in hudi-spark4-common. Spark 4.0 is 
kept
    separate: it pulls in Avro 1.12.0 (fast reader off) and lacks the ~71-line
    read-side java.time normalization that 4.1+ need for Avro 1.12.1, and that
    block does not parameterize cleanly. Because hudi-spark4.0.x depends on
    hudi-spark4-common, a same-named copy would collide on the classpath, so the
    4.0-only copy is renamed to Spark40AvroDeserializer (body unchanged).
    
    Consolidating also gives each file a unique repo path, so it re-enters the
    coverage denominator: the identically-pathed copies were dropped by
    report-path resolution and were invisible to Codecov.
    
    * Revert the 4.x Avro serde consolidation (unsound: vendored fork must 
shadow spark-sql per-module)
    
    Reverts commit 5a8169f05bbe. On Spark 4.x the avro connector is merged into
    the spark-sql artifact, so org.apache.spark.sql.avro.{AvroDeserializer,
    AvroSerializer,AvroUtils} ship inside spark-sql_2.13 itself (verified in the
    4.0.2/4.1.1/4.2.0-preview4 jars). spark-sql is a provided dependency of 
every
    4.x version module, so Spark's own AvroDeserializer/AvroSerializer are 
always
    on the 4.x compile classpath.
    
    The vendored fork shares Spark's FQN. While it lived as source in each 
version
    module, same-module source shadowed the spark-sql class during that module's
    compile. Moving it into the hudi-spark4-common jar turned it into a peer
    dependency-jar class that competes with spark-sql's copy, and spark-sql 
wins:
    the HoodieSpark4_xAvro{Deserializer,Serializer} wrappers then resolve the 
wrong
    class (compile error in 4.2.x: the 3-arg (Schema, DataType,
    LegacyBehaviorPolicy.Value) call cannot bind to Spark's constructors). Even
    where it compiled, the wrapper would silently link Spark's serde on a data
    path.
    
    The 3.x consolidation is kept: spark-sql/spark-catalyst 3.x do not contain 
the
    avro serde classes (they live only in the separate spark-avro artifact, 
which
    is not a dependency of the 3.x modules), so the vendored fork in
    hudi-spark3-common is the only such class on the 3.x classpath and resolves
    correctly. This revert restores the six 4.x serde files to be byte-identical
    to apache/master.
---
 .../apache/spark/sql/avro/AvroDeserializer.scala   |   7 +-
 .../org/apache/spark/sql/avro/AvroSerializer.scala |   9 +-
 .../apache/spark/sql/avro/AvroDeserializer.scala   | 531 ---------------------
 .../org/apache/spark/sql/avro/AvroSerializer.scala | 490 -------------------
 .../apache/spark/sql/avro/AvroDeserializer.scala   | 531 ---------------------
 .../org/apache/spark/sql/avro/AvroSerializer.scala | 489 -------------------
 6 files changed, 13 insertions(+), 2044 deletions(-)

diff --git 
a/hudi-spark-datasource/hudi-spark3.3.x/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
 
b/hudi-spark-datasource/hudi-spark3-common/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
similarity index 97%
rename from 
hudi-spark-datasource/hudi-spark3.3.x/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
rename to 
hudi-spark-datasource/hudi-spark3-common/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
index b5eba6be24cd..2a9244508182 100644
--- 
a/hudi-spark-datasource/hudi-spark3.3.x/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
+++ 
b/hudi-spark-datasource/hudi-spark3-common/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
@@ -33,7 +33,12 @@ import 
org.apache.spark.sql.catalyst.expressions.{SpecificInternalRow, UnsafeArr
 import org.apache.spark.sql.catalyst.util.{ArrayBasedMapData, ArrayData, 
DateTimeUtils, GenericArrayData, RebaseDateTime}
 import org.apache.spark.sql.catalyst.util.DateTimeConstants.MILLIS_PER_DAY
 import org.apache.spark.sql.execution.datasources.DataSourceUtils
-import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
+// LegacyBehaviorPolicy is nested in SQLConf on Spark 3.3/3.4 
(org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy)
+// but a top-level object on Spark 3.5 
(org.apache.spark.sql.internal.LegacyBehaviorPolicy). Importing both
+// containers via wildcards lets this single shared source resolve the enum on 
every 3.x version: exactly one of
+// the two wildcards contributes LegacyBehaviorPolicy on any given version, so 
there is no ambiguity.
+import org.apache.spark.sql.internal._
+import org.apache.spark.sql.internal.SQLConf._
 import org.apache.spark.sql.types._
 import org.apache.spark.unsafe.types.UTF8String
 
diff --git 
a/hudi-spark-datasource/hudi-spark3.3.x/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
 
b/hudi-spark-datasource/hudi-spark3-common/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
similarity index 97%
rename from 
hudi-spark-datasource/hudi-spark3.3.x/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
rename to 
hudi-spark-datasource/hudi-spark3-common/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
index a1241b72e58b..a432eec0ffed 100644
--- 
a/hudi-spark-datasource/hudi-spark3.3.x/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
+++ 
b/hudi-spark-datasource/hudi-spark3-common/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
@@ -34,8 +34,13 @@ import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.expressions.{SpecializedGetters, 
SpecificInternalRow}
 import org.apache.spark.sql.catalyst.util.{DateTimeUtils, RebaseDateTime}
 import org.apache.spark.sql.execution.datasources.DataSourceUtils
-import org.apache.spark.sql.internal.SQLConf
-import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
+// LegacyBehaviorPolicy is nested in SQLConf on Spark 3.3/3.4 
(org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy)
+// but a top-level object on Spark 3.5 
(org.apache.spark.sql.internal.LegacyBehaviorPolicy). Importing both
+// containers via wildcards lets this single shared source resolve the enum on 
every 3.x version: exactly one of
+// the two wildcards contributes LegacyBehaviorPolicy on any given version, so 
there is no ambiguity. The same two
+// wildcards also keep the SQLConf object in scope for the SQLConf.get / 
SQLConf.AVRO_REBASE_MODE_IN_WRITE usages.
+import org.apache.spark.sql.internal._
+import org.apache.spark.sql.internal.SQLConf._
 import org.apache.spark.sql.types._
 
 import java.nio.ByteBuffer
diff --git 
a/hudi-spark-datasource/hudi-spark3.4.x/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
 
b/hudi-spark-datasource/hudi-spark3.4.x/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
deleted file mode 100644
index b5eba6be24cd..000000000000
--- 
a/hudi-spark-datasource/hudi-spark3.4.x/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
+++ /dev/null
@@ -1,531 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.spark.sql.avro
-
-import org.apache.hudi.common.schema.HoodieSchema
-import org.apache.hudi.common.schema.HoodieSchema.VectorLogicalType
-
-import org.apache.avro.{LogicalTypes, Schema, SchemaBuilder}
-import org.apache.avro.Conversions.DecimalConversion
-import org.apache.avro.LogicalTypes.{LocalTimestampMicros, 
LocalTimestampMillis, TimestampMicros, TimestampMillis}
-import org.apache.avro.Schema.Type._
-import org.apache.avro.generic._
-import org.apache.avro.util.Utf8
-import org.apache.spark.sql.avro.AvroDeserializer.{createDateRebaseFuncInRead, 
createTimestampRebaseFuncInRead, RebaseSpec}
-import org.apache.spark.sql.avro.AvroUtils.{toFieldStr, AvroMatchedField}
-import org.apache.spark.sql.catalyst.{InternalRow, NoopFilters, StructFilters}
-import org.apache.spark.sql.catalyst.expressions.{SpecificInternalRow, 
UnsafeArrayData}
-import org.apache.spark.sql.catalyst.util.{ArrayBasedMapData, ArrayData, 
DateTimeUtils, GenericArrayData, RebaseDateTime}
-import org.apache.spark.sql.catalyst.util.DateTimeConstants.MILLIS_PER_DAY
-import org.apache.spark.sql.execution.datasources.DataSourceUtils
-import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
-import org.apache.spark.sql.types._
-import org.apache.spark.unsafe.types.UTF8String
-
-import java.math.BigDecimal
-import java.nio.ByteBuffer
-import java.nio.ByteOrder
-import java.util.TimeZone
-
-import scala.collection.JavaConverters._
-
-/**
- * A deserializer to deserialize data in avro format to data in catalyst 
format.
- *
- * NOTE: This code is borrowed from Spark 3.3.0
- * This code is borrowed, so that we can better control compatibility w/in 
Spark minor
- * branches (3.2.x, 3.1.x, etc)
- *
- * PLEASE REFRAIN MAKING ANY CHANGES TO THIS CODE UNLESS ABSOLUTELY NECESSARY
- */
-private[sql] class AvroDeserializer(rootAvroType: Schema,
-                                    rootCatalystType: DataType,
-                                    positionalFieldMatch: Boolean,
-                                    datetimeRebaseSpec: RebaseSpec,
-                                    filters: StructFilters) {
-
-  def this(rootAvroType: Schema,
-           rootCatalystType: DataType,
-           datetimeRebaseMode: String) = {
-    this(
-      rootAvroType,
-      rootCatalystType,
-      positionalFieldMatch = false,
-      RebaseSpec(LegacyBehaviorPolicy.withName(datetimeRebaseMode)),
-      new NoopFilters)
-  }
-
-  private lazy val decimalConversions = new DecimalConversion()
-
-  private val dateRebaseFunc = 
createDateRebaseFuncInRead(datetimeRebaseSpec.mode, "Avro")
-
-  private val timestampRebaseFunc = 
createTimestampRebaseFuncInRead(datetimeRebaseSpec, "Avro")
-
-  private val converter: Any => Option[Any] = try {
-    rootCatalystType match {
-      // A shortcut for empty schema.
-      case st: StructType if st.isEmpty =>
-        (_: Any) => Some(InternalRow.empty)
-
-      case st: StructType =>
-        val resultRow = new SpecificInternalRow(st.map(_.dataType))
-        val fieldUpdater = new RowUpdater(resultRow)
-        val applyFilters = filters.skipRow(resultRow, _)
-        val writer = getRecordWriter(rootAvroType, st, Nil, Nil, applyFilters)
-        (data: Any) => {
-          val record = data.asInstanceOf[GenericRecord]
-          val skipRow = writer(fieldUpdater, record)
-          if (skipRow) None else Some(resultRow)
-        }
-
-      case _ =>
-        val tmpRow = new SpecificInternalRow(Seq(rootCatalystType))
-        val fieldUpdater = new RowUpdater(tmpRow)
-        val writer = newWriter(rootAvroType, rootCatalystType, Nil, Nil)
-        (data: Any) => {
-          writer(fieldUpdater, 0, data)
-          Some(tmpRow.get(0, rootCatalystType))
-        }
-    }
-  } catch {
-    case ise: IncompatibleSchemaException => throw new 
IncompatibleSchemaException(
-      s"Cannot convert Avro type $rootAvroType to SQL type 
${rootCatalystType.sql}.", ise)
-  }
-
-  def deserialize(data: Any): Option[Any] = converter(data)
-
-  /**
-   * Creates a writer to write avro values to Catalyst values at the given 
ordinal with the given
-   * updater.
-   */
-  private def newWriter(avroType: Schema,
-                        catalystType: DataType,
-                        avroPath: Seq[String],
-                        catalystPath: Seq[String]): (CatalystDataUpdater, Int, 
Any) => Unit = {
-    val errorPrefix = s"Cannot convert Avro ${toFieldStr(avroPath)} to " +
-      s"SQL ${toFieldStr(catalystPath)} because "
-    val incompatibleMsg = errorPrefix +
-      s"schema is incompatible (avroType = $avroType, sqlType = 
${catalystType.sql})"
-
-    (avroType.getType, catalystType) match {
-      case (NULL, NullType) => (updater, ordinal, _) =>
-        updater.setNullAt(ordinal)
-
-      // TODO: we can avoid boxing if future version of avro provide primitive 
accessors.
-      case (BOOLEAN, BooleanType) => (updater, ordinal, value) =>
-        updater.setBoolean(ordinal, value.asInstanceOf[Boolean])
-
-      case (INT, IntegerType) => (updater, ordinal, value) =>
-        updater.setInt(ordinal, value.asInstanceOf[Int])
-
-      case (INT, DateType) => (updater, ordinal, value) =>
-        updater.setInt(ordinal, dateRebaseFunc(value.asInstanceOf[Int]))
-
-      case (LONG, LongType) => (updater, ordinal, value) =>
-        updater.setLong(ordinal, value.asInstanceOf[Long])
-
-      case (LONG, TimestampType) => avroType.getLogicalType match {
-        // For backward compatibility, if the Avro type is Long and it is not 
logical type
-        // (the `null` case), the value is processed as timestamp type with 
millisecond precision.
-        case null | _: TimestampMillis => (updater, ordinal, value) =>
-          val millis = value.asInstanceOf[Long]
-          val micros = DateTimeUtils.millisToMicros(millis)
-          updater.setLong(ordinal, timestampRebaseFunc(micros))
-        case _: TimestampMicros => (updater, ordinal, value) =>
-          val micros = value.asInstanceOf[Long]
-          updater.setLong(ordinal, timestampRebaseFunc(micros))
-        case other => throw new IncompatibleSchemaException(errorPrefix +
-          s"Avro logical type $other cannot be converted to SQL type 
${TimestampType.sql}.")
-      }
-
-      case (LONG, TimestampNTZType) => avroType.getLogicalType match {
-        // To keep consistent with TimestampType, if the Avro type is Long and 
it is not
-        // logical type (the `null` case), the value is processed as 
TimestampNTZ
-        // with millisecond precision.
-        case null | _: LocalTimestampMillis => (updater, ordinal, value) =>
-          val millis = value.asInstanceOf[Long]
-          val micros = DateTimeUtils.millisToMicros(millis)
-          updater.setLong(ordinal, micros)
-        case _: LocalTimestampMicros => (updater, ordinal, value) =>
-          val micros = value.asInstanceOf[Long]
-          updater.setLong(ordinal, micros)
-        case other => throw new IncompatibleSchemaException(errorPrefix +
-          s"Avro logical type $other cannot be converted to SQL type 
${TimestampNTZType.sql}.")
-      }
-
-      // Handle VECTOR logical type (FLOAT, DOUBLE, INT8)
-      case (FIXED, ArrayType(elementType, false)) => avroType.getLogicalType 
match {
-        case vectorLogicalType: VectorLogicalType =>
-          val dimension = vectorLogicalType.getDimension
-          val vecElementType = 
HoodieSchema.Vector.VectorElementType.fromString(vectorLogicalType.getElementType)
-          val elementSize = vecElementType.getElementSize
-          (updater, ordinal, value) => {
-            val bytes = value.asInstanceOf[GenericData.Fixed].bytes()
-            val expectedSize = Math.multiplyExact(dimension, elementSize)
-            if (bytes.length != expectedSize) {
-              throw new IncompatibleSchemaException(
-                s"VECTOR byte size mismatch: expected=$expectedSize, 
actual=${bytes.length}")
-            }
-            elementType match {
-              case FloatType =>
-                val buffer = 
ByteBuffer.wrap(bytes).order(VectorLogicalType.VECTOR_BYTE_ORDER)
-                val floats = new Array[Float](dimension)
-                var i = 0; while (i < dimension) { floats(i) = 
buffer.getFloat(); i += 1 }
-                updater.set(ordinal, ArrayData.toArrayData(floats))
-              case DoubleType =>
-                val buffer = 
ByteBuffer.wrap(bytes).order(VectorLogicalType.VECTOR_BYTE_ORDER)
-                val doubles = new Array[Double](dimension)
-                var i = 0; while (i < dimension) { doubles(i) = 
buffer.getDouble(); i += 1 }
-                updater.set(ordinal, ArrayData.toArrayData(doubles))
-              case ByteType =>
-                updater.set(ordinal, ArrayData.toArrayData(bytes.clone()))
-            }
-          }
-        case _ => throw new IncompatibleSchemaException(incompatibleMsg)
-      }
-
-      // Before we upgrade Avro to 1.8 for logical type support, spark-avro 
converts Long to Date.
-      // For backward compatibility, we still keep this conversion.
-      case (LONG, DateType) => (updater, ordinal, value) =>
-        updater.setInt(ordinal, (value.asInstanceOf[Long] / 
MILLIS_PER_DAY).toInt)
-
-      case (FLOAT, FloatType) => (updater, ordinal, value) =>
-        updater.setFloat(ordinal, value.asInstanceOf[Float])
-
-      case (DOUBLE, DoubleType) => (updater, ordinal, value) =>
-        updater.setDouble(ordinal, value.asInstanceOf[Double])
-
-      case (STRING, StringType) => (updater, ordinal, value) =>
-        val str = value match {
-          case s: String => UTF8String.fromString(s)
-          case s: Utf8 =>
-            val bytes = new Array[Byte](s.getByteLength)
-            System.arraycopy(s.getBytes, 0, bytes, 0, s.getByteLength)
-            UTF8String.fromBytes(bytes)
-          case s: GenericData.EnumSymbol => UTF8String.fromString(s.toString)
-        }
-        updater.set(ordinal, str)
-
-      case (ENUM, StringType) => (updater, ordinal, value) =>
-        updater.set(ordinal, UTF8String.fromString(value.toString))
-
-      case (FIXED, BinaryType) => (updater, ordinal, value) =>
-        updater.set(ordinal, value.asInstanceOf[GenericFixed].bytes().clone())
-
-      case (BYTES, BinaryType) => (updater, ordinal, value) =>
-        val bytes = value match {
-          case b: ByteBuffer =>
-            val bytes = new Array[Byte](b.remaining)
-            b.get(bytes)
-            // Do not forget to reset the position
-            b.rewind()
-            bytes
-          case b: Array[Byte] => b
-          case other =>
-            throw new RuntimeException(errorPrefix + s"$other is not a valid 
avro binary.")
-        }
-        updater.set(ordinal, bytes)
-
-      case (FIXED, _: DecimalType) => (updater, ordinal, value) =>
-        val d = avroType.getLogicalType.asInstanceOf[LogicalTypes.Decimal]
-        val bigDecimal = 
decimalConversions.fromFixed(value.asInstanceOf[GenericFixed], avroType, d)
-        val decimal = createDecimal(bigDecimal, d.getPrecision, d.getScale)
-        updater.setDecimal(ordinal, decimal)
-
-      case (BYTES, _: DecimalType) => (updater, ordinal, value) =>
-        val d = avroType.getLogicalType.asInstanceOf[LogicalTypes.Decimal]
-        val bigDecimal = 
decimalConversions.fromBytes(value.asInstanceOf[ByteBuffer], avroType, d)
-        val decimal = createDecimal(bigDecimal, d.getPrecision, d.getScale)
-        updater.setDecimal(ordinal, decimal)
-
-      case (RECORD, st: StructType) =>
-        // Avro datasource doesn't accept filters with nested attributes. See 
SPARK-32328.
-        // We can always return `false` from `applyFilters` for nested records.
-        val writeRecord =
-          getRecordWriter(avroType, st, avroPath, catalystPath, applyFilters = 
_ => false)
-        (updater, ordinal, value) =>
-          val row = new SpecificInternalRow(st)
-          writeRecord(new RowUpdater(row), value.asInstanceOf[GenericRecord])
-          updater.set(ordinal, row)
-
-      case (ARRAY, ArrayType(elementType, containsNull)) =>
-        val avroElementPath = avroPath :+ "element"
-        val elementWriter = newWriter(avroType.getElementType, elementType,
-          avroElementPath, catalystPath :+ "element")
-        (updater, ordinal, value) =>
-          val collection = value.asInstanceOf[java.util.Collection[Any]]
-          val result = createArrayData(elementType, collection.size())
-          val elementUpdater = new ArrayDataUpdater(result)
-
-          var i = 0
-          val iter = collection.iterator()
-          while (iter.hasNext) {
-            val element = iter.next()
-            if (element == null) {
-              if (!containsNull) {
-                throw new RuntimeException(
-                  s"Array value at path ${toFieldStr(avroElementPath)} is not 
allowed to be null")
-              } else {
-                elementUpdater.setNullAt(i)
-              }
-            } else {
-              elementWriter(elementUpdater, i, element)
-            }
-            i += 1
-          }
-
-          updater.set(ordinal, result)
-
-      case (MAP, MapType(keyType, valueType, valueContainsNull)) if keyType == 
StringType =>
-        val keyWriter = newWriter(SchemaBuilder.builder().stringType(), 
StringType,
-          avroPath :+ "key", catalystPath :+ "key")
-        val valueWriter = newWriter(avroType.getValueType, valueType,
-          avroPath :+ "value", catalystPath :+ "value")
-        (updater, ordinal, value) =>
-          val map = value.asInstanceOf[java.util.Map[AnyRef, AnyRef]]
-          val keyArray = createArrayData(keyType, map.size())
-          val keyUpdater = new ArrayDataUpdater(keyArray)
-          val valueArray = createArrayData(valueType, map.size())
-          val valueUpdater = new ArrayDataUpdater(valueArray)
-          val iter = map.entrySet().iterator()
-          var i = 0
-          while (iter.hasNext) {
-            val entry = iter.next()
-            assert(entry.getKey != null)
-            keyWriter(keyUpdater, i, entry.getKey)
-            if (entry.getValue == null) {
-              if (!valueContainsNull) {
-                throw new RuntimeException(
-                  s"Map value at path ${toFieldStr(avroPath :+ "value")} is 
not allowed to be null")
-              } else {
-                valueUpdater.setNullAt(i)
-              }
-            } else {
-              valueWriter(valueUpdater, i, entry.getValue)
-            }
-            i += 1
-          }
-
-          // The Avro map will never have null or duplicated map keys, it's 
safe to create a
-          // ArrayBasedMapData directly here.
-          updater.set(ordinal, new ArrayBasedMapData(keyArray, valueArray))
-
-      case (UNION, _) =>
-        val allTypes = avroType.getTypes.asScala
-        val nonNullTypes = allTypes.filter(_.getType != NULL)
-        val nonNullAvroType = Schema.createUnion(nonNullTypes.asJava)
-        if (nonNullTypes.nonEmpty) {
-          if (nonNullTypes.length == 1) {
-            newWriter(nonNullTypes.head, catalystType, avroPath, catalystPath)
-          } else {
-            nonNullTypes.map(_.getType).toSeq match {
-              case Seq(a, b) if Set(a, b) == Set(INT, LONG) && catalystType == 
LongType =>
-                (updater, ordinal, value) => value match {
-                  case null => updater.setNullAt(ordinal)
-                  case l: java.lang.Long => updater.setLong(ordinal, l)
-                  case i: java.lang.Integer => updater.setLong(ordinal, 
i.longValue())
-                }
-
-              case Seq(a, b) if Set(a, b) == Set(FLOAT, DOUBLE) && 
catalystType == DoubleType =>
-                (updater, ordinal, value) => value match {
-                  case null => updater.setNullAt(ordinal)
-                  case d: java.lang.Double => updater.setDouble(ordinal, d)
-                  case f: java.lang.Float => updater.setDouble(ordinal, 
f.doubleValue())
-                }
-
-              case _ =>
-                catalystType match {
-                  case st: StructType if st.length == nonNullTypes.size =>
-                    val fieldWriters = nonNullTypes.zip(st.fields).map {
-                      case (schema, field) =>
-                        newWriter(schema, field.dataType, avroPath, 
catalystPath :+ field.name)
-                    }.toArray
-                    (updater, ordinal, value) => {
-                      val row = new SpecificInternalRow(st)
-                      val fieldUpdater = new RowUpdater(row)
-                      val i = GenericData.get().resolveUnion(nonNullAvroType, 
value)
-                      fieldWriters(i)(fieldUpdater, i, value)
-                      updater.set(ordinal, row)
-                    }
-
-                  case _ => throw new 
IncompatibleSchemaException(incompatibleMsg)
-                }
-            }
-          }
-        } else {
-          (updater, ordinal, _) => updater.setNullAt(ordinal)
-        }
-
-      case (INT, _: YearMonthIntervalType) => (updater, ordinal, value) =>
-        updater.setInt(ordinal, value.asInstanceOf[Int])
-
-      case (LONG, _: DayTimeIntervalType) => (updater, ordinal, value) =>
-        updater.setLong(ordinal, value.asInstanceOf[Long])
-
-      case _ => throw new IncompatibleSchemaException(incompatibleMsg)
-    }
-  }
-
-  // TODO: move the following method in Decimal object on creating Decimal 
from BigDecimal?
-  private def createDecimal(decimal: BigDecimal, precision: Int, scale: Int): 
Decimal = {
-    if (precision <= Decimal.MAX_LONG_DIGITS) {
-      // Constructs a `Decimal` with an unscaled `Long` value if possible.
-      Decimal(decimal.unscaledValue().longValue(), precision, scale)
-    } else {
-      // Otherwise, resorts to an unscaled `BigInteger` instead.
-      Decimal(decimal, precision, scale)
-    }
-  }
-
-  private def getRecordWriter(
-                               avroType: Schema,
-                               catalystType: StructType,
-                               avroPath: Seq[String],
-                               catalystPath: Seq[String],
-                               applyFilters: Int => Boolean): 
(CatalystDataUpdater, GenericRecord) => Boolean = {
-
-    val avroSchemaHelper = new AvroUtils.AvroSchemaHelper(
-      avroType, catalystType, avroPath, catalystPath, positionalFieldMatch)
-
-    avroSchemaHelper.validateNoExtraCatalystFields(ignoreNullable = true)
-    // no need to validateNoExtraAvroFields since extra Avro fields are ignored
-
-    val (validFieldIndexes, fieldWriters) = avroSchemaHelper.matchedFields.map 
{
-      case AvroMatchedField(catalystField, ordinal, avroField) =>
-        val baseWriter = newWriter(avroField.schema(), catalystField.dataType,
-          avroPath :+ avroField.name, catalystPath :+ catalystField.name)
-        val fieldWriter = (fieldUpdater: CatalystDataUpdater, value: Any) => {
-          if (value == null) {
-            fieldUpdater.setNullAt(ordinal)
-          } else {
-            baseWriter(fieldUpdater, ordinal, value)
-          }
-        }
-        (avroField.pos(), fieldWriter)
-    }.toArray.unzip
-
-    (fieldUpdater, record) => {
-      var i = 0
-      var skipRow = false
-      while (i < validFieldIndexes.length && !skipRow) {
-        fieldWriters(i)(fieldUpdater, record.get(validFieldIndexes(i)))
-        skipRow = applyFilters(i)
-        i += 1
-      }
-      skipRow
-    }
-  }
-
-  private def createArrayData(elementType: DataType, length: Int): ArrayData = 
elementType match {
-    case BooleanType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Boolean](length))
-    case ByteType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Byte](length))
-    case ShortType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Short](length))
-    case IntegerType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Int](length))
-    case LongType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Long](length))
-    case FloatType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Float](length))
-    case DoubleType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Double](length))
-    case _ => new GenericArrayData(new Array[Any](length))
-  }
-
-  /**
-   * A base interface for updating values inside catalyst data structure like 
`InternalRow` and
-   * `ArrayData`.
-   */
-  sealed trait CatalystDataUpdater {
-    def set(ordinal: Int, value: Any): Unit
-
-    def setNullAt(ordinal: Int): Unit = set(ordinal, null)
-    def setBoolean(ordinal: Int, value: Boolean): Unit = set(ordinal, value)
-    def setByte(ordinal: Int, value: Byte): Unit = set(ordinal, value)
-    def setShort(ordinal: Int, value: Short): Unit = set(ordinal, value)
-    def setInt(ordinal: Int, value: Int): Unit = set(ordinal, value)
-    def setLong(ordinal: Int, value: Long): Unit = set(ordinal, value)
-    def setDouble(ordinal: Int, value: Double): Unit = set(ordinal, value)
-    def setFloat(ordinal: Int, value: Float): Unit = set(ordinal, value)
-    def setDecimal(ordinal: Int, value: Decimal): Unit = set(ordinal, value)
-  }
-
-  final class RowUpdater(row: InternalRow) extends CatalystDataUpdater {
-    override def set(ordinal: Int, value: Any): Unit = row.update(ordinal, 
value)
-
-    override def setNullAt(ordinal: Int): Unit = row.setNullAt(ordinal)
-    override def setBoolean(ordinal: Int, value: Boolean): Unit = 
row.setBoolean(ordinal, value)
-    override def setByte(ordinal: Int, value: Byte): Unit = 
row.setByte(ordinal, value)
-    override def setShort(ordinal: Int, value: Short): Unit = 
row.setShort(ordinal, value)
-    override def setInt(ordinal: Int, value: Int): Unit = row.setInt(ordinal, 
value)
-    override def setLong(ordinal: Int, value: Long): Unit = 
row.setLong(ordinal, value)
-    override def setDouble(ordinal: Int, value: Double): Unit = 
row.setDouble(ordinal, value)
-    override def setFloat(ordinal: Int, value: Float): Unit = 
row.setFloat(ordinal, value)
-    override def setDecimal(ordinal: Int, value: Decimal): Unit =
-      row.setDecimal(ordinal, value, value.precision)
-  }
-
-  final class ArrayDataUpdater(array: ArrayData) extends CatalystDataUpdater {
-    override def set(ordinal: Int, value: Any): Unit = array.update(ordinal, 
value)
-
-    override def setNullAt(ordinal: Int): Unit = array.setNullAt(ordinal)
-    override def setBoolean(ordinal: Int, value: Boolean): Unit = 
array.setBoolean(ordinal, value)
-    override def setByte(ordinal: Int, value: Byte): Unit = 
array.setByte(ordinal, value)
-    override def setShort(ordinal: Int, value: Short): Unit = 
array.setShort(ordinal, value)
-    override def setInt(ordinal: Int, value: Int): Unit = 
array.setInt(ordinal, value)
-    override def setLong(ordinal: Int, value: Long): Unit = 
array.setLong(ordinal, value)
-    override def setDouble(ordinal: Int, value: Double): Unit = 
array.setDouble(ordinal, value)
-    override def setFloat(ordinal: Int, value: Float): Unit = 
array.setFloat(ordinal, value)
-    override def setDecimal(ordinal: Int, value: Decimal): Unit = 
array.update(ordinal, value)
-  }
-}
-
-object AvroDeserializer {
-
-  // NOTE: Following methods have been renamed in Spark 3.2.1 [1] making 
[[AvroDeserializer]] implementation
-  //       (which relies on it) be only compatible with the exact same version 
of [[DataSourceUtils]].
-  //       To make sure this implementation is compatible w/ all Spark 
versions w/in Spark 3.2.x branch,
-  //       we're preemptively cloned those methods to make sure Hudi is 
compatible w/ Spark 3.2.0 as well as
-  //       w/ Spark >= 3.2.1
-  //
-  // [1] https://github.com/apache/spark/pull/34978
-
-  // Specification of rebase operation including `mode` and the time zone in 
which it is performed
-  case class RebaseSpec(mode: LegacyBehaviorPolicy.Value, originTimeZone: 
Option[String] = None) {
-    // Use the default JVM time zone for backward compatibility
-    def timeZone: String = originTimeZone.getOrElse(TimeZone.getDefault.getID)
-  }
-
-  def createDateRebaseFuncInRead(rebaseMode: LegacyBehaviorPolicy.Value,
-                                 format: String): Int => Int = rebaseMode 
match {
-    case LegacyBehaviorPolicy.EXCEPTION => days: Int =>
-      if (days < RebaseDateTime.lastSwitchJulianDay) {
-        throw DataSourceUtils.newRebaseExceptionInRead(format)
-      }
-      days
-    case LegacyBehaviorPolicy.LEGACY => 
RebaseDateTime.rebaseJulianToGregorianDays
-    case LegacyBehaviorPolicy.CORRECTED => identity[Int]
-  }
-
-  def createTimestampRebaseFuncInRead(rebaseSpec: RebaseSpec,
-                                      format: String): Long => Long = 
rebaseSpec.mode match {
-    case LegacyBehaviorPolicy.EXCEPTION => micros: Long =>
-      if (micros < RebaseDateTime.lastSwitchJulianTs) {
-        throw DataSourceUtils.newRebaseExceptionInRead(format)
-      }
-      micros
-    case LegacyBehaviorPolicy.LEGACY => micros: Long =>
-      
RebaseDateTime.rebaseJulianToGregorianMicros(TimeZone.getTimeZone(rebaseSpec.timeZone),
 micros)
-    case LegacyBehaviorPolicy.CORRECTED => identity[Long]
-  }
-}
diff --git 
a/hudi-spark-datasource/hudi-spark3.4.x/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
 
b/hudi-spark-datasource/hudi-spark3.4.x/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
deleted file mode 100644
index a1241b72e58b..000000000000
--- 
a/hudi-spark-datasource/hudi-spark3.4.x/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.spark.sql.avro
-
-import org.apache.hudi.common.schema.HoodieSchema
-import org.apache.hudi.common.schema.HoodieSchema.VectorLogicalType
-
-import org.apache.avro.{LogicalTypes, Schema}
-import org.apache.avro.Conversions.DecimalConversion
-import org.apache.avro.LogicalTypes.{LocalTimestampMicros, 
LocalTimestampMillis, TimestampMicros, TimestampMillis}
-import org.apache.avro.Schema.Type
-import org.apache.avro.Schema.Type._
-import org.apache.avro.generic.GenericData.{EnumSymbol, Fixed, Record}
-import org.apache.avro.util.Utf8
-import org.apache.spark.internal.Logging
-import org.apache.spark.sql.avro.AvroSerializer.{createDateRebaseFuncInWrite, 
createTimestampRebaseFuncInWrite}
-import org.apache.spark.sql.avro.AvroUtils.{toFieldStr, AvroMatchedField}
-import org.apache.spark.sql.catalyst.InternalRow
-import org.apache.spark.sql.catalyst.expressions.{SpecializedGetters, 
SpecificInternalRow}
-import org.apache.spark.sql.catalyst.util.{DateTimeUtils, RebaseDateTime}
-import org.apache.spark.sql.execution.datasources.DataSourceUtils
-import org.apache.spark.sql.internal.SQLConf
-import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
-import org.apache.spark.sql.types._
-
-import java.nio.ByteBuffer
-import java.nio.ByteOrder
-import java.util.TimeZone
-
-import scala.collection.JavaConverters._
-
-/**
- * A serializer to serialize data in catalyst format to data in avro format.
- *
- * NOTE: This code is borrowed from Spark 3.3.0
- *       This code is borrowed, so that we can better control compatibility 
w/in Spark minor
- *       branches (3.2.x, 3.1.x, etc)
- *
- * NOTE: THIS IMPLEMENTATION HAS BEEN MODIFIED FROM ITS ORIGINAL VERSION WITH 
THE MODIFICATION
- *       BEING EXPLICITLY ANNOTATED INLINE. PLEASE MAKE SURE TO UNDERSTAND 
PROPERLY ALL THE
- *       MODIFICATIONS.
- *
- * PLEASE REFRAIN MAKING ANY CHANGES TO THIS CODE UNLESS ABSOLUTELY NECESSARY
- */
-private[sql] class AvroSerializer(rootCatalystType: DataType,
-                                  rootAvroType: Schema,
-                                  nullable: Boolean,
-                                  positionalFieldMatch: Boolean,
-                                  datetimeRebaseMode: 
LegacyBehaviorPolicy.Value) extends Logging {
-
-  def this(rootCatalystType: DataType, rootAvroType: Schema, nullable: 
Boolean) = {
-    this(rootCatalystType, rootAvroType, nullable, positionalFieldMatch = 
false,
-      
LegacyBehaviorPolicy.withName(SQLConf.get.getConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE,
-        LegacyBehaviorPolicy.CORRECTED.toString)))
-  }
-
-  def serialize(catalystData: Any): Any = {
-    converter.apply(catalystData)
-  }
-
-  private val dateRebaseFunc = createDateRebaseFuncInWrite(
-    datetimeRebaseMode, "Avro")
-
-  private val timestampRebaseFunc = createTimestampRebaseFuncInWrite(
-    datetimeRebaseMode, "Avro")
-
-  private val converter: Any => Any = {
-    val actualAvroType = resolveNullableType(rootAvroType, nullable)
-    val baseConverter = try {
-      rootCatalystType match {
-        case st: StructType =>
-          newStructConverter(st, actualAvroType, Nil, Nil).asInstanceOf[Any => 
Any]
-        case _ =>
-          val tmpRow = new SpecificInternalRow(Seq(rootCatalystType))
-          val converter = newConverter(rootCatalystType, actualAvroType, Nil, 
Nil)
-          (data: Any) =>
-            tmpRow.update(0, data)
-            converter.apply(tmpRow, 0)
-      }
-    } catch {
-      case ise: IncompatibleSchemaException => throw new 
IncompatibleSchemaException(
-        s"Cannot convert SQL type ${rootCatalystType.sql} to Avro type 
$rootAvroType.", ise)
-    }
-    if (nullable) {
-      (data: Any) =>
-        if (data == null) {
-          null
-        } else {
-          baseConverter.apply(data)
-        }
-    } else {
-      baseConverter
-    }
-  }
-
-  private type Converter = (SpecializedGetters, Int) => Any
-
-  private lazy val decimalConversions = new DecimalConversion()
-
-  private def newConverter(catalystType: DataType,
-                           avroType: Schema,
-                           catalystPath: Seq[String],
-                           avroPath: Seq[String]): Converter = {
-    val errorPrefix = s"Cannot convert SQL ${toFieldStr(catalystPath)} " +
-      s"to Avro ${toFieldStr(avroPath)} because "
-    (catalystType, avroType.getType) match {
-      case (NullType, NULL) =>
-        (getter, ordinal) => null
-      case (BooleanType, BOOLEAN) =>
-        (getter, ordinal) => getter.getBoolean(ordinal)
-      case (ByteType, INT) =>
-        (getter, ordinal) => getter.getByte(ordinal).toInt
-      case (ShortType, INT) =>
-        (getter, ordinal) => getter.getShort(ordinal).toInt
-      case (IntegerType, INT) =>
-        (getter, ordinal) => getter.getInt(ordinal)
-      case (LongType, LONG) =>
-        (getter, ordinal) => getter.getLong(ordinal)
-      case (FloatType, FLOAT) =>
-        (getter, ordinal) => getter.getFloat(ordinal)
-      case (DoubleType, DOUBLE) =>
-        (getter, ordinal) => getter.getDouble(ordinal)
-      case (d: DecimalType, FIXED)
-        if avroType.getLogicalType == LogicalTypes.decimal(d.precision, 
d.scale) =>
-        (getter, ordinal) =>
-          val decimal = getter.getDecimal(ordinal, d.precision, d.scale)
-          decimalConversions.toFixed(decimal.toJavaBigDecimal, avroType,
-            LogicalTypes.decimal(d.precision, d.scale))
-
-      case (d: DecimalType, BYTES)
-        if avroType.getLogicalType == LogicalTypes.decimal(d.precision, 
d.scale) =>
-        (getter, ordinal) =>
-          val decimal = getter.getDecimal(ordinal, d.precision, d.scale)
-          decimalConversions.toBytes(decimal.toJavaBigDecimal, avroType,
-            LogicalTypes.decimal(d.precision, d.scale))
-
-      // Handle VECTOR logical type (FLOAT, DOUBLE, INT8)
-      case (ArrayType(elementType, false), FIXED) => avroType.getLogicalType 
match {
-        case vectorLogicalType: VectorLogicalType =>
-          val dimension = vectorLogicalType.getDimension
-          val vecElementType = 
HoodieSchema.Vector.VectorElementType.fromString(vectorLogicalType.getElementType)
-          val bufferSize = Math.multiplyExact(dimension, 
vecElementType.getElementSize)
-          (getter, ordinal) => {
-            val arrayData = getter.getArray(ordinal)
-            if (arrayData.numElements() != dimension) {
-              throw new IncompatibleSchemaException(
-                s"VECTOR dimension mismatch at ${toFieldStr(catalystPath)}: " +
-                s"expected=$dimension, actual=${arrayData.numElements()}")
-            }
-            elementType match {
-              case FloatType =>
-                val buffer = 
ByteBuffer.allocate(bufferSize).order(VectorLogicalType.VECTOR_BYTE_ORDER)
-                var i = 0; while (i < dimension) { 
buffer.putFloat(arrayData.getFloat(i)); i += 1 }
-                new Fixed(avroType, buffer.array())
-              case DoubleType =>
-                val buffer = 
ByteBuffer.allocate(bufferSize).order(VectorLogicalType.VECTOR_BYTE_ORDER)
-                var i = 0; while (i < dimension) { 
buffer.putDouble(arrayData.getDouble(i)); i += 1 }
-                new Fixed(avroType, buffer.array())
-              case ByteType =>
-                val bytes = new Array[Byte](dimension)
-                var i = 0; while (i < dimension) { bytes(i) = 
arrayData.getByte(i); i += 1 }
-                new Fixed(avroType, bytes)
-              case _ => throw new IncompatibleSchemaException(errorPrefix +
-                s"schema is incompatible (sqlType = ${catalystType.sql}, 
avroType = $avroType)")
-            }
-          }
-        case _ => throw new IncompatibleSchemaException(errorPrefix +
-          s"schema is incompatible (sqlType = ${catalystType.sql}, avroType = 
$avroType)")
-      }
-
-      case (StringType, ENUM) =>
-        val enumSymbols: Set[String] = avroType.getEnumSymbols.asScala.toSet
-        (getter, ordinal) =>
-          val data = getter.getUTF8String(ordinal).toString
-          if (!enumSymbols.contains(data)) {
-            throw new IncompatibleSchemaException(errorPrefix +
-              s""""$data" cannot be written since it's not defined in enum """ 
+
-              enumSymbols.mkString("\"", "\", \"", "\""))
-          }
-          new EnumSymbol(avroType, data)
-
-      case (StringType, STRING) =>
-        (getter, ordinal) => new Utf8(getter.getUTF8String(ordinal).getBytes)
-
-      case (BinaryType, FIXED) =>
-        val size = avroType.getFixedSize
-        (getter, ordinal) =>
-          val data: Array[Byte] = getter.getBinary(ordinal)
-          if (data.length != size) {
-            def len2str(len: Int): String = s"$len ${if (len > 1) "bytes" else 
"byte"}"
-
-            throw new IncompatibleSchemaException(errorPrefix + 
len2str(data.length) +
-              " of binary data cannot be written into FIXED type with size of 
" + len2str(size))
-          }
-          new Fixed(avroType, data)
-
-      case (BinaryType, BYTES) =>
-        (getter, ordinal) => ByteBuffer.wrap(getter.getBinary(ordinal))
-
-      case (DateType, INT) =>
-        (getter, ordinal) => dateRebaseFunc(getter.getInt(ordinal))
-
-      case (TimestampType, LONG) => avroType.getLogicalType match {
-        // For backward compatibility, if the Avro type is Long and it is not 
logical type
-        // (the `null` case), output the timestamp value as with millisecond 
precision.
-        case null | _: TimestampMillis => (getter, ordinal) =>
-          
DateTimeUtils.microsToMillis(timestampRebaseFunc(getter.getLong(ordinal)))
-        case _: TimestampMicros => (getter, ordinal) =>
-          timestampRebaseFunc(getter.getLong(ordinal))
-        case other => throw new IncompatibleSchemaException(errorPrefix +
-          s"SQL type ${TimestampType.sql} cannot be converted to Avro logical 
type $other")
-      }
-
-      case (TimestampNTZType, LONG) => avroType.getLogicalType match {
-        // To keep consistent with TimestampType, if the Avro type is Long and 
it is not
-        // logical type (the `null` case), output the TimestampNTZ as long 
value
-        // in millisecond precision.
-        case null | _: LocalTimestampMillis => (getter, ordinal) =>
-          DateTimeUtils.microsToMillis(getter.getLong(ordinal))
-        case _: LocalTimestampMicros => (getter, ordinal) =>
-          getter.getLong(ordinal)
-        case other => throw new IncompatibleSchemaException(errorPrefix +
-          s"SQL type ${TimestampNTZType.sql} cannot be converted to Avro 
logical type $other")
-      }
-
-      case (ArrayType(et, containsNull), ARRAY) =>
-        val elementConverter = newConverter(
-          et, resolveNullableType(avroType.getElementType, containsNull),
-          catalystPath :+ "element", avroPath :+ "element")
-        (getter, ordinal) => {
-          val arrayData = getter.getArray(ordinal)
-          val len = arrayData.numElements()
-          val result = new Array[Any](len)
-          var i = 0
-          while (i < len) {
-            if (containsNull && arrayData.isNullAt(i)) {
-              result(i) = null
-            } else {
-              result(i) = elementConverter(arrayData, i)
-            }
-            i += 1
-          }
-          // avro writer is expecting a Java Collection, so we convert it into
-          // `ArrayList` backed by the specified array without data copying.
-          java.util.Arrays.asList(result: _*)
-        }
-
-      case (st: StructType, RECORD) =>
-        val structConverter = newStructConverter(st, avroType, catalystPath, 
avroPath)
-        val numFields = st.length
-        (getter, ordinal) => structConverter(getter.getStruct(ordinal, 
numFields))
-
-      
////////////////////////////////////////////////////////////////////////////////////////////
-      // Following section is amended to the original (Spark's) implementation
-      // >>> BEGINS
-      
////////////////////////////////////////////////////////////////////////////////////////////
-
-      case (st: StructType, UNION) =>
-        val unionConverter = newUnionConverter(st, avroType, catalystPath, 
avroPath)
-        val numFields = st.length
-        (getter, ordinal) => unionConverter(getter.getStruct(ordinal, 
numFields))
-
-      
////////////////////////////////////////////////////////////////////////////////////////////
-      // <<< ENDS
-      
////////////////////////////////////////////////////////////////////////////////////////////
-
-      case (MapType(kt, vt, valueContainsNull), MAP) if kt == StringType =>
-        val valueConverter = newConverter(
-          vt, resolveNullableType(avroType.getValueType, valueContainsNull),
-          catalystPath :+ "value", avroPath :+ "value")
-        (getter, ordinal) =>
-          val mapData = getter.getMap(ordinal)
-          val len = mapData.numElements()
-          val result = new java.util.HashMap[String, Any](len)
-          val keyArray = mapData.keyArray()
-          val valueArray = mapData.valueArray()
-          var i = 0
-          while (i < len) {
-            val key = keyArray.getUTF8String(i).toString
-            if (valueContainsNull && valueArray.isNullAt(i)) {
-              result.put(key, null)
-            } else {
-              result.put(key, valueConverter(valueArray, i))
-            }
-            i += 1
-          }
-          result
-
-      case (_: YearMonthIntervalType, INT) =>
-        (getter, ordinal) => getter.getInt(ordinal)
-
-      case (_: DayTimeIntervalType, LONG) =>
-        (getter, ordinal) => getter.getLong(ordinal)
-
-      case _ =>
-        throw new IncompatibleSchemaException(errorPrefix +
-          s"schema is incompatible (sqlType = ${catalystType.sql}, avroType = 
$avroType)")
-    }
-  }
-
-  private def newStructConverter(catalystStruct: StructType,
-                                 avroStruct: Schema,
-                                 catalystPath: Seq[String],
-                                 avroPath: Seq[String]): InternalRow => Record 
= {
-
-    val avroSchemaHelper = new AvroUtils.AvroSchemaHelper(
-      avroStruct, catalystStruct, avroPath, catalystPath, positionalFieldMatch)
-
-    avroSchemaHelper.validateNoExtraCatalystFields(ignoreNullable = false)
-    avroSchemaHelper.validateNoExtraRequiredAvroFields()
-
-    val (avroIndices, fieldConverters) = avroSchemaHelper.matchedFields.map {
-      case AvroMatchedField(catalystField, _, avroField) =>
-        val converter = newConverter(catalystField.dataType,
-          resolveNullableType(avroField.schema(), catalystField.nullable),
-          catalystPath :+ catalystField.name, avroPath :+ avroField.name)
-        (avroField.pos(), converter)
-    }.toArray.unzip
-
-    val numFields = catalystStruct.length
-    row: InternalRow =>
-      val result = new Record(avroStruct)
-      var i = 0
-      while (i < numFields) {
-        if (row.isNullAt(i)) {
-          result.put(avroIndices(i), null)
-        } else {
-          result.put(avroIndices(i), fieldConverters(i).apply(row, i))
-        }
-        i += 1
-      }
-      result
-  }
-
-  
////////////////////////////////////////////////////////////////////////////////////////////
-  // Following section is amended to the original (Spark's) implementation
-  // >>> BEGINS
-  
////////////////////////////////////////////////////////////////////////////////////////////
-
-  private def newUnionConverter(catalystStruct: StructType,
-                                avroUnion: Schema,
-                                catalystPath: Seq[String],
-                                avroPath: Seq[String]): InternalRow => Any = {
-    if (avroUnion.getType != UNION || !canMapUnion(catalystStruct, avroUnion)) 
{
-      throw new IncompatibleSchemaException(s"Cannot convert Catalyst type 
$catalystStruct to " +
-        s"Avro type $avroUnion.")
-    }
-    val nullable = avroUnion.getTypes.size() > 0 && 
avroUnion.getTypes.get(0).getType == Type.NULL
-    val avroInnerTypes = if (nullable) {
-      avroUnion.getTypes.asScala.tail
-    } else {
-      avroUnion.getTypes.asScala
-    }
-    val fieldConverters = catalystStruct.zip(avroInnerTypes).map {
-      case (f1, f2) => newConverter(f1.dataType, f2, catalystPath, avroPath)
-    }
-    val numFields = catalystStruct.length
-    (row: InternalRow) =>
-      var i = 0
-      var result: Any = null
-      while (i < numFields) {
-        if (!row.isNullAt(i)) {
-          if (result != null) {
-            throw new IncompatibleSchemaException(s"Cannot convert Catalyst 
record $catalystStruct to " +
-              s"Avro union $avroUnion. Record has more than one optional 
values set")
-          }
-          result = fieldConverters(i).apply(row, i)
-        }
-        i += 1
-      }
-      if (!nullable && result == null) {
-        throw new IncompatibleSchemaException(s"Cannot convert Catalyst record 
$catalystStruct to " +
-          s"Avro union $avroUnion. Record has no values set, while should have 
exactly one")
-      }
-      result
-  }
-
-  private def canMapUnion(catalystStruct: StructType, avroStruct: Schema): 
Boolean = {
-    (avroStruct.getTypes.size() > 0 &&
-      avroStruct.getTypes.get(0).getType == Type.NULL &&
-      avroStruct.getTypes.size() - 1 == catalystStruct.length) || 
avroStruct.getTypes.size() == catalystStruct.length
-  }
-
-  
////////////////////////////////////////////////////////////////////////////////////////////
-  // <<< ENDS
-  
////////////////////////////////////////////////////////////////////////////////////////////
-
-
-  /**
-   * Resolve a possibly nullable Avro Type.
-   *
-   * An Avro type is nullable when it is a [[UNION]] of two types: one null 
type and another
-   * non-null type. This method will check the nullability of the input Avro 
type and return the
-   * non-null type within when it is nullable. Otherwise it will return the 
input Avro type
-   * unchanged. It will throw an [[UnsupportedAvroTypeException]] when the 
input Avro type is an
-   * unsupported nullable type.
-   *
-   * It will also log a warning message if the nullability for Avro and 
catalyst types are
-   * different.
-   */
-  private def resolveNullableType(avroType: Schema, nullable: Boolean): Schema 
= {
-    val (avroNullable, resolvedAvroType) = resolveAvroType(avroType)
-    warnNullabilityDifference(avroNullable, nullable)
-    resolvedAvroType
-  }
-
-  /**
-   * Check the nullability of the input Avro type and resolve it when it is 
nullable. The first
-   * return value is a [[Boolean]] indicating if the input Avro type is 
nullable. The second
-   * return value is the possibly resolved type.
-   */
-  private def resolveAvroType(avroType: Schema): (Boolean, Schema) = {
-    if (avroType.getType == Type.UNION) {
-      val fields = avroType.getTypes.asScala
-      val actualType = fields.filter(_.getType != Type.NULL)
-      if (fields.length == 2 && actualType.length == 1) {
-        (true, actualType.head)
-      } else {
-        // This is just a normal union, not used to designate nullability
-        (false, avroType)
-      }
-    } else {
-      (false, avroType)
-    }
-  }
-
-  /**
-   * log a warning message if the nullability for Avro and catalyst types are 
different.
-   */
-  private def warnNullabilityDifference(avroNullable: Boolean, 
catalystNullable: Boolean): Unit = {
-    if (avroNullable && !catalystNullable) {
-      logWarning("Writing Avro files with nullable Avro schema and 
non-nullable catalyst schema.")
-    }
-    if (!avroNullable && catalystNullable) {
-      logWarning("Writing Avro files with non-nullable Avro schema and 
nullable catalyst " +
-        "schema will throw runtime exception if there is a record with null 
value.")
-    }
-  }
-}
-
-object AvroSerializer {
-
-  // NOTE: Following methods have been renamed in Spark 3.2.1 [1] making 
[[AvroSerializer]] implementation
-  //       (which relies on it) be only compatible with the exact same version 
of [[DataSourceUtils]].
-  //       To make sure this implementation is compatible w/ all Spark 
versions w/in Spark 3.2.x branch,
-  //       we're preemptively cloned those methods to make sure Hudi is 
compatible w/ Spark 3.2.0 as well as
-  //       w/ Spark >= 3.2.1
-  //
-  // [1] https://github.com/apache/spark/pull/34978
-
-  def createDateRebaseFuncInWrite(rebaseMode: LegacyBehaviorPolicy.Value,
-                                  format: String): Int => Int = rebaseMode 
match {
-    case LegacyBehaviorPolicy.EXCEPTION => days: Int =>
-      if (days < RebaseDateTime.lastSwitchGregorianDay) {
-        throw DataSourceUtils.newRebaseExceptionInWrite(format)
-      }
-      days
-    case LegacyBehaviorPolicy.LEGACY => 
RebaseDateTime.rebaseGregorianToJulianDays
-    case LegacyBehaviorPolicy.CORRECTED => identity[Int]
-  }
-
-  def createTimestampRebaseFuncInWrite(rebaseMode: LegacyBehaviorPolicy.Value,
-                                       format: String): Long => Long = 
rebaseMode match {
-    case LegacyBehaviorPolicy.EXCEPTION => micros: Long =>
-      if (micros < RebaseDateTime.lastSwitchGregorianTs) {
-        throw DataSourceUtils.newRebaseExceptionInWrite(format)
-      }
-      micros
-    case LegacyBehaviorPolicy.LEGACY =>
-      val timeZone = SQLConf.get.sessionLocalTimeZone
-      
RebaseDateTime.rebaseGregorianToJulianMicros(TimeZone.getTimeZone(timeZone), _)
-    case LegacyBehaviorPolicy.CORRECTED => identity[Long]
-  }
-
-}
diff --git 
a/hudi-spark-datasource/hudi-spark3.5.x/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
 
b/hudi-spark-datasource/hudi-spark3.5.x/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
deleted file mode 100644
index d10ff1edfe87..000000000000
--- 
a/hudi-spark-datasource/hudi-spark3.5.x/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
+++ /dev/null
@@ -1,531 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.spark.sql.avro
-
-import org.apache.hudi.common.schema.HoodieSchema
-import org.apache.hudi.common.schema.HoodieSchema.VectorLogicalType
-
-import org.apache.avro.{LogicalTypes, Schema, SchemaBuilder}
-import org.apache.avro.Conversions.DecimalConversion
-import org.apache.avro.LogicalTypes.{LocalTimestampMicros, 
LocalTimestampMillis, TimestampMicros, TimestampMillis}
-import org.apache.avro.Schema.Type._
-import org.apache.avro.generic._
-import org.apache.avro.util.Utf8
-import org.apache.spark.sql.avro.AvroDeserializer.{createDateRebaseFuncInRead, 
createTimestampRebaseFuncInRead, RebaseSpec}
-import org.apache.spark.sql.avro.AvroUtils.{toFieldStr, AvroMatchedField}
-import org.apache.spark.sql.catalyst.{InternalRow, NoopFilters, StructFilters}
-import org.apache.spark.sql.catalyst.expressions.{SpecificInternalRow, 
UnsafeArrayData}
-import org.apache.spark.sql.catalyst.util.{ArrayBasedMapData, ArrayData, 
DateTimeUtils, GenericArrayData, RebaseDateTime}
-import org.apache.spark.sql.catalyst.util.DateTimeConstants.MILLIS_PER_DAY
-import org.apache.spark.sql.execution.datasources.DataSourceUtils
-import org.apache.spark.sql.internal.LegacyBehaviorPolicy
-import org.apache.spark.sql.types._
-import org.apache.spark.unsafe.types.UTF8String
-
-import java.math.BigDecimal
-import java.nio.ByteBuffer
-import java.nio.ByteOrder
-import java.util.TimeZone
-
-import scala.collection.JavaConverters._
-
-/**
- * A deserializer to deserialize data in avro format to data in catalyst 
format.
- *
- * NOTE: This code is borrowed from Spark 3.3.0
- * This code is borrowed, so that we can better control compatibility w/in 
Spark minor
- * branches (3.2.x, 3.1.x, etc)
- *
- * PLEASE REFRAIN MAKING ANY CHANGES TO THIS CODE UNLESS ABSOLUTELY NECESSARY
- */
-private[sql] class AvroDeserializer(rootAvroType: Schema,
-                                    rootCatalystType: DataType,
-                                    positionalFieldMatch: Boolean,
-                                    datetimeRebaseSpec: RebaseSpec,
-                                    filters: StructFilters) {
-
-  def this(rootAvroType: Schema,
-           rootCatalystType: DataType,
-           datetimeRebaseMode: String) = {
-    this(
-      rootAvroType,
-      rootCatalystType,
-      positionalFieldMatch = false,
-      RebaseSpec(LegacyBehaviorPolicy.withName(datetimeRebaseMode)),
-      new NoopFilters)
-  }
-
-  private lazy val decimalConversions = new DecimalConversion()
-
-  private val dateRebaseFunc = 
createDateRebaseFuncInRead(datetimeRebaseSpec.mode, "Avro")
-
-  private val timestampRebaseFunc = 
createTimestampRebaseFuncInRead(datetimeRebaseSpec, "Avro")
-
-  private val converter: Any => Option[Any] = try {
-    rootCatalystType match {
-      // A shortcut for empty schema.
-      case st: StructType if st.isEmpty =>
-        (_: Any) => Some(InternalRow.empty)
-
-      case st: StructType =>
-        val resultRow = new SpecificInternalRow(st.map(_.dataType))
-        val fieldUpdater = new RowUpdater(resultRow)
-        val applyFilters = filters.skipRow(resultRow, _)
-        val writer = getRecordWriter(rootAvroType, st, Nil, Nil, applyFilters)
-        (data: Any) => {
-          val record = data.asInstanceOf[GenericRecord]
-          val skipRow = writer(fieldUpdater, record)
-          if (skipRow) None else Some(resultRow)
-        }
-
-      case _ =>
-        val tmpRow = new SpecificInternalRow(Seq(rootCatalystType))
-        val fieldUpdater = new RowUpdater(tmpRow)
-        val writer = newWriter(rootAvroType, rootCatalystType, Nil, Nil)
-        (data: Any) => {
-          writer(fieldUpdater, 0, data)
-          Some(tmpRow.get(0, rootCatalystType))
-        }
-    }
-  } catch {
-    case ise: IncompatibleSchemaException => throw new 
IncompatibleSchemaException(
-      s"Cannot convert Avro type $rootAvroType to SQL type 
${rootCatalystType.sql}.", ise)
-  }
-
-  def deserialize(data: Any): Option[Any] = converter(data)
-
-  /**
-   * Creates a writer to write avro values to Catalyst values at the given 
ordinal with the given
-   * updater.
-   */
-  private def newWriter(avroType: Schema,
-                        catalystType: DataType,
-                        avroPath: Seq[String],
-                        catalystPath: Seq[String]): (CatalystDataUpdater, Int, 
Any) => Unit = {
-    val errorPrefix = s"Cannot convert Avro ${toFieldStr(avroPath)} to " +
-      s"SQL ${toFieldStr(catalystPath)} because "
-    val incompatibleMsg = errorPrefix +
-      s"schema is incompatible (avroType = $avroType, sqlType = 
${catalystType.sql})"
-
-    (avroType.getType, catalystType) match {
-      case (NULL, NullType) => (updater, ordinal, _) =>
-        updater.setNullAt(ordinal)
-
-      // TODO: we can avoid boxing if future version of avro provide primitive 
accessors.
-      case (BOOLEAN, BooleanType) => (updater, ordinal, value) =>
-        updater.setBoolean(ordinal, value.asInstanceOf[Boolean])
-
-      case (INT, IntegerType) => (updater, ordinal, value) =>
-        updater.setInt(ordinal, value.asInstanceOf[Int])
-
-      case (INT, DateType) => (updater, ordinal, value) =>
-        updater.setInt(ordinal, dateRebaseFunc(value.asInstanceOf[Int]))
-
-      case (LONG, LongType) => (updater, ordinal, value) =>
-        updater.setLong(ordinal, value.asInstanceOf[Long])
-
-      case (LONG, TimestampType) => avroType.getLogicalType match {
-        // For backward compatibility, if the Avro type is Long and it is not 
logical type
-        // (the `null` case), the value is processed as timestamp type with 
millisecond precision.
-        case null | _: TimestampMillis => (updater, ordinal, value) =>
-          val millis = value.asInstanceOf[Long]
-          val micros = DateTimeUtils.millisToMicros(millis)
-          updater.setLong(ordinal, timestampRebaseFunc(micros))
-        case _: TimestampMicros => (updater, ordinal, value) =>
-          val micros = value.asInstanceOf[Long]
-          updater.setLong(ordinal, timestampRebaseFunc(micros))
-        case other => throw new IncompatibleSchemaException(errorPrefix +
-          s"Avro logical type $other cannot be converted to SQL type 
${TimestampType.sql}.")
-      }
-
-      case (LONG, TimestampNTZType) => avroType.getLogicalType match {
-        // To keep consistent with TimestampType, if the Avro type is Long and 
it is not
-        // logical type (the `null` case), the value is processed as 
TimestampNTZ
-        // with millisecond precision.
-        case null | _: LocalTimestampMillis => (updater, ordinal, value) =>
-          val millis = value.asInstanceOf[Long]
-          val micros = DateTimeUtils.millisToMicros(millis)
-          updater.setLong(ordinal, micros)
-        case _: LocalTimestampMicros => (updater, ordinal, value) =>
-          val micros = value.asInstanceOf[Long]
-          updater.setLong(ordinal, micros)
-        case other => throw new IncompatibleSchemaException(errorPrefix +
-          s"Avro logical type $other cannot be converted to SQL type 
${TimestampNTZType.sql}.")
-      }
-
-      // Handle VECTOR logical type (FLOAT, DOUBLE, INT8)
-      case (FIXED, ArrayType(elementType, false)) => avroType.getLogicalType 
match {
-        case vectorLogicalType: VectorLogicalType =>
-          val dimension = vectorLogicalType.getDimension
-          val vecElementType = 
HoodieSchema.Vector.VectorElementType.fromString(vectorLogicalType.getElementType)
-          val elementSize = vecElementType.getElementSize
-          (updater, ordinal, value) => {
-            val bytes = value.asInstanceOf[GenericData.Fixed].bytes()
-            val expectedSize = Math.multiplyExact(dimension, elementSize)
-            if (bytes.length != expectedSize) {
-              throw new IncompatibleSchemaException(
-                s"VECTOR byte size mismatch: expected=$expectedSize, 
actual=${bytes.length}")
-            }
-            elementType match {
-              case FloatType =>
-                val buffer = 
ByteBuffer.wrap(bytes).order(VectorLogicalType.VECTOR_BYTE_ORDER)
-                val floats = new Array[Float](dimension)
-                var i = 0; while (i < dimension) { floats(i) = 
buffer.getFloat(); i += 1 }
-                updater.set(ordinal, ArrayData.toArrayData(floats))
-              case DoubleType =>
-                val buffer = 
ByteBuffer.wrap(bytes).order(VectorLogicalType.VECTOR_BYTE_ORDER)
-                val doubles = new Array[Double](dimension)
-                var i = 0; while (i < dimension) { doubles(i) = 
buffer.getDouble(); i += 1 }
-                updater.set(ordinal, ArrayData.toArrayData(doubles))
-              case ByteType =>
-                updater.set(ordinal, ArrayData.toArrayData(bytes.clone()))
-            }
-          }
-        case _ => throw new IncompatibleSchemaException(incompatibleMsg)
-      }
-
-      // Before we upgrade Avro to 1.8 for logical type support, spark-avro 
converts Long to Date.
-      // For backward compatibility, we still keep this conversion.
-      case (LONG, DateType) => (updater, ordinal, value) =>
-        updater.setInt(ordinal, (value.asInstanceOf[Long] / 
MILLIS_PER_DAY).toInt)
-
-      case (FLOAT, FloatType) => (updater, ordinal, value) =>
-        updater.setFloat(ordinal, value.asInstanceOf[Float])
-
-      case (DOUBLE, DoubleType) => (updater, ordinal, value) =>
-        updater.setDouble(ordinal, value.asInstanceOf[Double])
-
-      case (STRING, StringType) => (updater, ordinal, value) =>
-        val str = value match {
-          case s: String => UTF8String.fromString(s)
-          case s: Utf8 =>
-            val bytes = new Array[Byte](s.getByteLength)
-            System.arraycopy(s.getBytes, 0, bytes, 0, s.getByteLength)
-            UTF8String.fromBytes(bytes)
-          case s: GenericData.EnumSymbol => UTF8String.fromString(s.toString)
-        }
-        updater.set(ordinal, str)
-
-      case (ENUM, StringType) => (updater, ordinal, value) =>
-        updater.set(ordinal, UTF8String.fromString(value.toString))
-
-      case (FIXED, BinaryType) => (updater, ordinal, value) =>
-        updater.set(ordinal, value.asInstanceOf[GenericFixed].bytes().clone())
-
-      case (BYTES, BinaryType) => (updater, ordinal, value) =>
-        val bytes = value match {
-          case b: ByteBuffer =>
-            val bytes = new Array[Byte](b.remaining)
-            b.get(bytes)
-            // Do not forget to reset the position
-            b.rewind()
-            bytes
-          case b: Array[Byte] => b
-          case other =>
-            throw new RuntimeException(errorPrefix + s"$other is not a valid 
avro binary.")
-        }
-        updater.set(ordinal, bytes)
-
-      case (FIXED, _: DecimalType) => (updater, ordinal, value) =>
-        val d = avroType.getLogicalType.asInstanceOf[LogicalTypes.Decimal]
-        val bigDecimal = 
decimalConversions.fromFixed(value.asInstanceOf[GenericFixed], avroType, d)
-        val decimal = createDecimal(bigDecimal, d.getPrecision, d.getScale)
-        updater.setDecimal(ordinal, decimal)
-
-      case (BYTES, _: DecimalType) => (updater, ordinal, value) =>
-        val d = avroType.getLogicalType.asInstanceOf[LogicalTypes.Decimal]
-        val bigDecimal = 
decimalConversions.fromBytes(value.asInstanceOf[ByteBuffer], avroType, d)
-        val decimal = createDecimal(bigDecimal, d.getPrecision, d.getScale)
-        updater.setDecimal(ordinal, decimal)
-
-      case (RECORD, st: StructType) =>
-        // Avro datasource doesn't accept filters with nested attributes. See 
SPARK-32328.
-        // We can always return `false` from `applyFilters` for nested records.
-        val writeRecord =
-          getRecordWriter(avroType, st, avroPath, catalystPath, applyFilters = 
_ => false)
-        (updater, ordinal, value) =>
-          val row = new SpecificInternalRow(st)
-          writeRecord(new RowUpdater(row), value.asInstanceOf[GenericRecord])
-          updater.set(ordinal, row)
-
-      case (ARRAY, ArrayType(elementType, containsNull)) =>
-        val avroElementPath = avroPath :+ "element"
-        val elementWriter = newWriter(avroType.getElementType, elementType,
-          avroElementPath, catalystPath :+ "element")
-        (updater, ordinal, value) =>
-          val collection = value.asInstanceOf[java.util.Collection[Any]]
-          val result = createArrayData(elementType, collection.size())
-          val elementUpdater = new ArrayDataUpdater(result)
-
-          var i = 0
-          val iter = collection.iterator()
-          while (iter.hasNext) {
-            val element = iter.next()
-            if (element == null) {
-              if (!containsNull) {
-                throw new RuntimeException(
-                  s"Array value at path ${toFieldStr(avroElementPath)} is not 
allowed to be null")
-              } else {
-                elementUpdater.setNullAt(i)
-              }
-            } else {
-              elementWriter(elementUpdater, i, element)
-            }
-            i += 1
-          }
-
-          updater.set(ordinal, result)
-
-      case (MAP, MapType(keyType, valueType, valueContainsNull)) if keyType == 
StringType =>
-        val keyWriter = newWriter(SchemaBuilder.builder().stringType(), 
StringType,
-          avroPath :+ "key", catalystPath :+ "key")
-        val valueWriter = newWriter(avroType.getValueType, valueType,
-          avroPath :+ "value", catalystPath :+ "value")
-        (updater, ordinal, value) =>
-          val map = value.asInstanceOf[java.util.Map[AnyRef, AnyRef]]
-          val keyArray = createArrayData(keyType, map.size())
-          val keyUpdater = new ArrayDataUpdater(keyArray)
-          val valueArray = createArrayData(valueType, map.size())
-          val valueUpdater = new ArrayDataUpdater(valueArray)
-          val iter = map.entrySet().iterator()
-          var i = 0
-          while (iter.hasNext) {
-            val entry = iter.next()
-            assert(entry.getKey != null)
-            keyWriter(keyUpdater, i, entry.getKey)
-            if (entry.getValue == null) {
-              if (!valueContainsNull) {
-                throw new RuntimeException(
-                  s"Map value at path ${toFieldStr(avroPath :+ "value")} is 
not allowed to be null")
-              } else {
-                valueUpdater.setNullAt(i)
-              }
-            } else {
-              valueWriter(valueUpdater, i, entry.getValue)
-            }
-            i += 1
-          }
-
-          // The Avro map will never have null or duplicated map keys, it's 
safe to create a
-          // ArrayBasedMapData directly here.
-          updater.set(ordinal, new ArrayBasedMapData(keyArray, valueArray))
-
-      case (UNION, _) =>
-        val allTypes = avroType.getTypes.asScala
-        val nonNullTypes = allTypes.filter(_.getType != NULL)
-        val nonNullAvroType = Schema.createUnion(nonNullTypes.asJava)
-        if (nonNullTypes.nonEmpty) {
-          if (nonNullTypes.length == 1) {
-            newWriter(nonNullTypes.head, catalystType, avroPath, catalystPath)
-          } else {
-            nonNullTypes.map(_.getType).toSeq match {
-              case Seq(a, b) if Set(a, b) == Set(INT, LONG) && catalystType == 
LongType =>
-                (updater, ordinal, value) => value match {
-                  case null => updater.setNullAt(ordinal)
-                  case l: java.lang.Long => updater.setLong(ordinal, l)
-                  case i: java.lang.Integer => updater.setLong(ordinal, 
i.longValue())
-                }
-
-              case Seq(a, b) if Set(a, b) == Set(FLOAT, DOUBLE) && 
catalystType == DoubleType =>
-                (updater, ordinal, value) => value match {
-                  case null => updater.setNullAt(ordinal)
-                  case d: java.lang.Double => updater.setDouble(ordinal, d)
-                  case f: java.lang.Float => updater.setDouble(ordinal, 
f.doubleValue())
-                }
-
-              case _ =>
-                catalystType match {
-                  case st: StructType if st.length == nonNullTypes.size =>
-                    val fieldWriters = nonNullTypes.zip(st.fields).map {
-                      case (schema, field) =>
-                        newWriter(schema, field.dataType, avroPath, 
catalystPath :+ field.name)
-                    }.toArray
-                    (updater, ordinal, value) => {
-                      val row = new SpecificInternalRow(st)
-                      val fieldUpdater = new RowUpdater(row)
-                      val i = GenericData.get().resolveUnion(nonNullAvroType, 
value)
-                      fieldWriters(i)(fieldUpdater, i, value)
-                      updater.set(ordinal, row)
-                    }
-
-                  case _ => throw new 
IncompatibleSchemaException(incompatibleMsg)
-                }
-            }
-          }
-        } else {
-          (updater, ordinal, _) => updater.setNullAt(ordinal)
-        }
-
-      case (INT, _: YearMonthIntervalType) => (updater, ordinal, value) =>
-        updater.setInt(ordinal, value.asInstanceOf[Int])
-
-      case (LONG, _: DayTimeIntervalType) => (updater, ordinal, value) =>
-        updater.setLong(ordinal, value.asInstanceOf[Long])
-
-      case _ => throw new IncompatibleSchemaException(incompatibleMsg)
-    }
-  }
-
-  // TODO: move the following method in Decimal object on creating Decimal 
from BigDecimal?
-  private def createDecimal(decimal: BigDecimal, precision: Int, scale: Int): 
Decimal = {
-    if (precision <= Decimal.MAX_LONG_DIGITS) {
-      // Constructs a `Decimal` with an unscaled `Long` value if possible.
-      Decimal(decimal.unscaledValue().longValue(), precision, scale)
-    } else {
-      // Otherwise, resorts to an unscaled `BigInteger` instead.
-      Decimal(decimal, precision, scale)
-    }
-  }
-
-  private def getRecordWriter(
-                               avroType: Schema,
-                               catalystType: StructType,
-                               avroPath: Seq[String],
-                               catalystPath: Seq[String],
-                               applyFilters: Int => Boolean): 
(CatalystDataUpdater, GenericRecord) => Boolean = {
-
-    val avroSchemaHelper = new AvroUtils.AvroSchemaHelper(
-      avroType, catalystType, avroPath, catalystPath, positionalFieldMatch)
-
-    avroSchemaHelper.validateNoExtraCatalystFields(ignoreNullable = true)
-    // no need to validateNoExtraAvroFields since extra Avro fields are ignored
-
-    val (validFieldIndexes, fieldWriters) = avroSchemaHelper.matchedFields.map 
{
-      case AvroMatchedField(catalystField, ordinal, avroField) =>
-        val baseWriter = newWriter(avroField.schema(), catalystField.dataType,
-          avroPath :+ avroField.name, catalystPath :+ catalystField.name)
-        val fieldWriter = (fieldUpdater: CatalystDataUpdater, value: Any) => {
-          if (value == null) {
-            fieldUpdater.setNullAt(ordinal)
-          } else {
-            baseWriter(fieldUpdater, ordinal, value)
-          }
-        }
-        (avroField.pos(), fieldWriter)
-    }.toArray.unzip
-
-    (fieldUpdater, record) => {
-      var i = 0
-      var skipRow = false
-      while (i < validFieldIndexes.length && !skipRow) {
-        fieldWriters(i)(fieldUpdater, record.get(validFieldIndexes(i)))
-        skipRow = applyFilters(i)
-        i += 1
-      }
-      skipRow
-    }
-  }
-
-  private def createArrayData(elementType: DataType, length: Int): ArrayData = 
elementType match {
-    case BooleanType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Boolean](length))
-    case ByteType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Byte](length))
-    case ShortType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Short](length))
-    case IntegerType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Int](length))
-    case LongType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Long](length))
-    case FloatType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Float](length))
-    case DoubleType => UnsafeArrayData.fromPrimitiveArray(new 
Array[Double](length))
-    case _ => new GenericArrayData(new Array[Any](length))
-  }
-
-  /**
-   * A base interface for updating values inside catalyst data structure like 
`InternalRow` and
-   * `ArrayData`.
-   */
-  sealed trait CatalystDataUpdater {
-    def set(ordinal: Int, value: Any): Unit
-
-    def setNullAt(ordinal: Int): Unit = set(ordinal, null)
-    def setBoolean(ordinal: Int, value: Boolean): Unit = set(ordinal, value)
-    def setByte(ordinal: Int, value: Byte): Unit = set(ordinal, value)
-    def setShort(ordinal: Int, value: Short): Unit = set(ordinal, value)
-    def setInt(ordinal: Int, value: Int): Unit = set(ordinal, value)
-    def setLong(ordinal: Int, value: Long): Unit = set(ordinal, value)
-    def setDouble(ordinal: Int, value: Double): Unit = set(ordinal, value)
-    def setFloat(ordinal: Int, value: Float): Unit = set(ordinal, value)
-    def setDecimal(ordinal: Int, value: Decimal): Unit = set(ordinal, value)
-  }
-
-  final class RowUpdater(row: InternalRow) extends CatalystDataUpdater {
-    override def set(ordinal: Int, value: Any): Unit = row.update(ordinal, 
value)
-
-    override def setNullAt(ordinal: Int): Unit = row.setNullAt(ordinal)
-    override def setBoolean(ordinal: Int, value: Boolean): Unit = 
row.setBoolean(ordinal, value)
-    override def setByte(ordinal: Int, value: Byte): Unit = 
row.setByte(ordinal, value)
-    override def setShort(ordinal: Int, value: Short): Unit = 
row.setShort(ordinal, value)
-    override def setInt(ordinal: Int, value: Int): Unit = row.setInt(ordinal, 
value)
-    override def setLong(ordinal: Int, value: Long): Unit = 
row.setLong(ordinal, value)
-    override def setDouble(ordinal: Int, value: Double): Unit = 
row.setDouble(ordinal, value)
-    override def setFloat(ordinal: Int, value: Float): Unit = 
row.setFloat(ordinal, value)
-    override def setDecimal(ordinal: Int, value: Decimal): Unit =
-      row.setDecimal(ordinal, value, value.precision)
-  }
-
-  final class ArrayDataUpdater(array: ArrayData) extends CatalystDataUpdater {
-    override def set(ordinal: Int, value: Any): Unit = array.update(ordinal, 
value)
-
-    override def setNullAt(ordinal: Int): Unit = array.setNullAt(ordinal)
-    override def setBoolean(ordinal: Int, value: Boolean): Unit = 
array.setBoolean(ordinal, value)
-    override def setByte(ordinal: Int, value: Byte): Unit = 
array.setByte(ordinal, value)
-    override def setShort(ordinal: Int, value: Short): Unit = 
array.setShort(ordinal, value)
-    override def setInt(ordinal: Int, value: Int): Unit = 
array.setInt(ordinal, value)
-    override def setLong(ordinal: Int, value: Long): Unit = 
array.setLong(ordinal, value)
-    override def setDouble(ordinal: Int, value: Double): Unit = 
array.setDouble(ordinal, value)
-    override def setFloat(ordinal: Int, value: Float): Unit = 
array.setFloat(ordinal, value)
-    override def setDecimal(ordinal: Int, value: Decimal): Unit = 
array.update(ordinal, value)
-  }
-}
-
-object AvroDeserializer {
-
-  // NOTE: Following methods have been renamed in Spark 3.2.1 [1] making 
[[AvroDeserializer]] implementation
-  //       (which relies on it) be only compatible with the exact same version 
of [[DataSourceUtils]].
-  //       To make sure this implementation is compatible w/ all Spark 
versions w/in Spark 3.2.x branch,
-  //       we're preemptively cloned those methods to make sure Hudi is 
compatible w/ Spark 3.2.0 as well as
-  //       w/ Spark >= 3.2.1
-  //
-  // [1] https://github.com/apache/spark/pull/34978
-
-  // Specification of rebase operation including `mode` and the time zone in 
which it is performed
-  case class RebaseSpec(mode: LegacyBehaviorPolicy.Value, originTimeZone: 
Option[String] = None) {
-    // Use the default JVM time zone for backward compatibility
-    def timeZone: String = originTimeZone.getOrElse(TimeZone.getDefault.getID)
-  }
-
-  def createDateRebaseFuncInRead(rebaseMode: LegacyBehaviorPolicy.Value,
-                                 format: String): Int => Int = rebaseMode 
match {
-    case LegacyBehaviorPolicy.EXCEPTION => days: Int =>
-      if (days < RebaseDateTime.lastSwitchJulianDay) {
-        throw DataSourceUtils.newRebaseExceptionInRead(format)
-      }
-      days
-    case LegacyBehaviorPolicy.LEGACY => 
RebaseDateTime.rebaseJulianToGregorianDays
-    case LegacyBehaviorPolicy.CORRECTED => identity[Int]
-  }
-
-  def createTimestampRebaseFuncInRead(rebaseSpec: RebaseSpec,
-                                      format: String): Long => Long = 
rebaseSpec.mode match {
-    case LegacyBehaviorPolicy.EXCEPTION => micros: Long =>
-      if (micros < RebaseDateTime.lastSwitchJulianTs) {
-        throw DataSourceUtils.newRebaseExceptionInRead(format)
-      }
-      micros
-    case LegacyBehaviorPolicy.LEGACY => micros: Long =>
-      
RebaseDateTime.rebaseJulianToGregorianMicros(TimeZone.getTimeZone(rebaseSpec.timeZone),
 micros)
-    case LegacyBehaviorPolicy.CORRECTED => identity[Long]
-  }
-}
diff --git 
a/hudi-spark-datasource/hudi-spark3.5.x/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
 
b/hudi-spark-datasource/hudi-spark3.5.x/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
deleted file mode 100644
index 756ef82a55d2..000000000000
--- 
a/hudi-spark-datasource/hudi-spark3.5.x/src/main/scala/org/apache/spark/sql/avro/AvroSerializer.scala
+++ /dev/null
@@ -1,489 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.spark.sql.avro
-
-import org.apache.hudi.common.schema.HoodieSchema
-import org.apache.hudi.common.schema.HoodieSchema.VectorLogicalType
-
-import org.apache.avro.{LogicalTypes, Schema}
-import org.apache.avro.Conversions.DecimalConversion
-import org.apache.avro.LogicalTypes.{LocalTimestampMicros, 
LocalTimestampMillis, TimestampMicros, TimestampMillis}
-import org.apache.avro.Schema.Type
-import org.apache.avro.Schema.Type._
-import org.apache.avro.generic.GenericData.{EnumSymbol, Fixed, Record}
-import org.apache.avro.util.Utf8
-import org.apache.spark.internal.Logging
-import org.apache.spark.sql.avro.AvroSerializer.{createDateRebaseFuncInWrite, 
createTimestampRebaseFuncInWrite}
-import org.apache.spark.sql.avro.AvroUtils.{toFieldStr, AvroMatchedField}
-import org.apache.spark.sql.catalyst.InternalRow
-import org.apache.spark.sql.catalyst.expressions.{SpecializedGetters, 
SpecificInternalRow}
-import org.apache.spark.sql.catalyst.util.{DateTimeUtils, RebaseDateTime}
-import org.apache.spark.sql.execution.datasources.DataSourceUtils
-import org.apache.spark.sql.internal.{LegacyBehaviorPolicy, SQLConf}
-import org.apache.spark.sql.types._
-
-import java.nio.ByteBuffer
-import java.nio.ByteOrder
-import java.util.TimeZone
-
-import scala.collection.JavaConverters._
-
-/**
- * A serializer to serialize data in catalyst format to data in avro format.
- *
- * NOTE: This code is borrowed from Spark 3.3.0
- *       This code is borrowed, so that we can better control compatibility 
w/in Spark minor
- *       branches (3.2.x, 3.1.x, etc)
- *
- * NOTE: THIS IMPLEMENTATION HAS BEEN MODIFIED FROM ITS ORIGINAL VERSION WITH 
THE MODIFICATION
- *       BEING EXPLICITLY ANNOTATED INLINE. PLEASE MAKE SURE TO UNDERSTAND 
PROPERLY ALL THE
- *       MODIFICATIONS.
- *
- * PLEASE REFRAIN MAKING ANY CHANGES TO THIS CODE UNLESS ABSOLUTELY NECESSARY
- */
-private[sql] class AvroSerializer(rootCatalystType: DataType,
-                                  rootAvroType: Schema,
-                                  nullable: Boolean,
-                                  positionalFieldMatch: Boolean,
-                                  datetimeRebaseMode: 
LegacyBehaviorPolicy.Value) extends Logging {
-
-  def this(rootCatalystType: DataType, rootAvroType: Schema, nullable: 
Boolean) = {
-    this(rootCatalystType, rootAvroType, nullable, positionalFieldMatch = 
false,
-      
LegacyBehaviorPolicy.withName(SQLConf.get.getConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE,
-        LegacyBehaviorPolicy.CORRECTED.toString)))
-  }
-
-  def serialize(catalystData: Any): Any = {
-    converter.apply(catalystData)
-  }
-
-  private val dateRebaseFunc = createDateRebaseFuncInWrite(
-    datetimeRebaseMode, "Avro")
-
-  private val timestampRebaseFunc = createTimestampRebaseFuncInWrite(
-    datetimeRebaseMode, "Avro")
-
-  private val converter: Any => Any = {
-    val actualAvroType = resolveNullableType(rootAvroType, nullable)
-    val baseConverter = try {
-      rootCatalystType match {
-        case st: StructType =>
-          newStructConverter(st, actualAvroType, Nil, Nil).asInstanceOf[Any => 
Any]
-        case _ =>
-          val tmpRow = new SpecificInternalRow(Seq(rootCatalystType))
-          val converter = newConverter(rootCatalystType, actualAvroType, Nil, 
Nil)
-          (data: Any) =>
-            tmpRow.update(0, data)
-            converter.apply(tmpRow, 0)
-      }
-    } catch {
-      case ise: IncompatibleSchemaException => throw new 
IncompatibleSchemaException(
-        s"Cannot convert SQL type ${rootCatalystType.sql} to Avro type 
$rootAvroType.", ise)
-    }
-    if (nullable) {
-      (data: Any) =>
-        if (data == null) {
-          null
-        } else {
-          baseConverter.apply(data)
-        }
-    } else {
-      baseConverter
-    }
-  }
-
-  private type Converter = (SpecializedGetters, Int) => Any
-
-  private lazy val decimalConversions = new DecimalConversion()
-
-  private def newConverter(catalystType: DataType,
-                           avroType: Schema,
-                           catalystPath: Seq[String],
-                           avroPath: Seq[String]): Converter = {
-    val errorPrefix = s"Cannot convert SQL ${toFieldStr(catalystPath)} " +
-      s"to Avro ${toFieldStr(avroPath)} because "
-    (catalystType, avroType.getType) match {
-      case (NullType, NULL) =>
-        (getter, ordinal) => null
-      case (BooleanType, BOOLEAN) =>
-        (getter, ordinal) => getter.getBoolean(ordinal)
-      case (ByteType, INT) =>
-        (getter, ordinal) => getter.getByte(ordinal).toInt
-      case (ShortType, INT) =>
-        (getter, ordinal) => getter.getShort(ordinal).toInt
-      case (IntegerType, INT) =>
-        (getter, ordinal) => getter.getInt(ordinal)
-      case (LongType, LONG) =>
-        (getter, ordinal) => getter.getLong(ordinal)
-      case (FloatType, FLOAT) =>
-        (getter, ordinal) => getter.getFloat(ordinal)
-      case (DoubleType, DOUBLE) =>
-        (getter, ordinal) => getter.getDouble(ordinal)
-      case (d: DecimalType, FIXED)
-        if avroType.getLogicalType == LogicalTypes.decimal(d.precision, 
d.scale) =>
-        (getter, ordinal) =>
-          val decimal = getter.getDecimal(ordinal, d.precision, d.scale)
-          decimalConversions.toFixed(decimal.toJavaBigDecimal, avroType,
-            LogicalTypes.decimal(d.precision, d.scale))
-
-      case (d: DecimalType, BYTES)
-        if avroType.getLogicalType == LogicalTypes.decimal(d.precision, 
d.scale) =>
-        (getter, ordinal) =>
-          val decimal = getter.getDecimal(ordinal, d.precision, d.scale)
-          decimalConversions.toBytes(decimal.toJavaBigDecimal, avroType,
-            LogicalTypes.decimal(d.precision, d.scale))
-
-      // Handle VECTOR logical type (FLOAT, DOUBLE, INT8)
-      case (ArrayType(elementType, false), FIXED) => avroType.getLogicalType 
match {
-        case vectorLogicalType: VectorLogicalType =>
-          val dimension = vectorLogicalType.getDimension
-          val vecElementType = 
HoodieSchema.Vector.VectorElementType.fromString(vectorLogicalType.getElementType)
-          val bufferSize = Math.multiplyExact(dimension, 
vecElementType.getElementSize)
-          (getter, ordinal) => {
-            val arrayData = getter.getArray(ordinal)
-            if (arrayData.numElements() != dimension) {
-              throw new IncompatibleSchemaException(
-                s"VECTOR dimension mismatch at ${toFieldStr(catalystPath)}: " +
-                s"expected=$dimension, actual=${arrayData.numElements()}")
-            }
-            elementType match {
-              case FloatType =>
-                val buffer = 
ByteBuffer.allocate(bufferSize).order(VectorLogicalType.VECTOR_BYTE_ORDER)
-                var i = 0; while (i < dimension) { 
buffer.putFloat(arrayData.getFloat(i)); i += 1 }
-                new Fixed(avroType, buffer.array())
-              case DoubleType =>
-                val buffer = 
ByteBuffer.allocate(bufferSize).order(VectorLogicalType.VECTOR_BYTE_ORDER)
-                var i = 0; while (i < dimension) { 
buffer.putDouble(arrayData.getDouble(i)); i += 1 }
-                new Fixed(avroType, buffer.array())
-              case ByteType =>
-                val bytes = new Array[Byte](dimension)
-                var i = 0; while (i < dimension) { bytes(i) = 
arrayData.getByte(i); i += 1 }
-                new Fixed(avroType, bytes)
-              case _ => throw new IncompatibleSchemaException(errorPrefix +
-                s"schema is incompatible (sqlType = ${catalystType.sql}, 
avroType = $avroType)")
-            }
-          }
-        case _ => throw new IncompatibleSchemaException(errorPrefix +
-          s"schema is incompatible (sqlType = ${catalystType.sql}, avroType = 
$avroType)")
-      }
-
-      case (StringType, ENUM) =>
-        val enumSymbols: Set[String] = avroType.getEnumSymbols.asScala.toSet
-        (getter, ordinal) =>
-          val data = getter.getUTF8String(ordinal).toString
-          if (!enumSymbols.contains(data)) {
-            throw new IncompatibleSchemaException(errorPrefix +
-              s""""$data" cannot be written since it's not defined in enum """ 
+
-              enumSymbols.mkString("\"", "\", \"", "\""))
-          }
-          new EnumSymbol(avroType, data)
-
-      case (StringType, STRING) =>
-        (getter, ordinal) => new Utf8(getter.getUTF8String(ordinal).getBytes)
-
-      case (BinaryType, FIXED) =>
-        val size = avroType.getFixedSize
-        (getter, ordinal) =>
-          val data: Array[Byte] = getter.getBinary(ordinal)
-          if (data.length != size) {
-            def len2str(len: Int): String = s"$len ${if (len > 1) "bytes" else 
"byte"}"
-
-            throw new IncompatibleSchemaException(errorPrefix + 
len2str(data.length) +
-              " of binary data cannot be written into FIXED type with size of 
" + len2str(size))
-          }
-          new Fixed(avroType, data)
-
-      case (BinaryType, BYTES) =>
-        (getter, ordinal) => ByteBuffer.wrap(getter.getBinary(ordinal))
-
-      case (DateType, INT) =>
-        (getter, ordinal) => dateRebaseFunc(getter.getInt(ordinal))
-
-      case (TimestampType, LONG) => avroType.getLogicalType match {
-        // For backward compatibility, if the Avro type is Long and it is not 
logical type
-        // (the `null` case), output the timestamp value as with millisecond 
precision.
-        case null | _: TimestampMillis => (getter, ordinal) =>
-          
DateTimeUtils.microsToMillis(timestampRebaseFunc(getter.getLong(ordinal)))
-        case _: TimestampMicros => (getter, ordinal) =>
-          timestampRebaseFunc(getter.getLong(ordinal))
-        case other => throw new IncompatibleSchemaException(errorPrefix +
-          s"SQL type ${TimestampType.sql} cannot be converted to Avro logical 
type $other")
-      }
-
-      case (TimestampNTZType, LONG) => avroType.getLogicalType match {
-        // To keep consistent with TimestampType, if the Avro type is Long and 
it is not
-        // logical type (the `null` case), output the TimestampNTZ as long 
value
-        // in millisecond precision.
-        case null | _: LocalTimestampMillis => (getter, ordinal) =>
-          DateTimeUtils.microsToMillis(getter.getLong(ordinal))
-        case _: LocalTimestampMicros => (getter, ordinal) =>
-          getter.getLong(ordinal)
-        case other => throw new IncompatibleSchemaException(errorPrefix +
-          s"SQL type ${TimestampNTZType.sql} cannot be converted to Avro 
logical type $other")
-      }
-
-      case (ArrayType(et, containsNull), ARRAY) =>
-        val elementConverter = newConverter(
-          et, resolveNullableType(avroType.getElementType, containsNull),
-          catalystPath :+ "element", avroPath :+ "element")
-        (getter, ordinal) => {
-          val arrayData = getter.getArray(ordinal)
-          val len = arrayData.numElements()
-          val result = new Array[Any](len)
-          var i = 0
-          while (i < len) {
-            if (containsNull && arrayData.isNullAt(i)) {
-              result(i) = null
-            } else {
-              result(i) = elementConverter(arrayData, i)
-            }
-            i += 1
-          }
-          // avro writer is expecting a Java Collection, so we convert it into
-          // `ArrayList` backed by the specified array without data copying.
-          java.util.Arrays.asList(result: _*)
-        }
-
-      case (st: StructType, RECORD) =>
-        val structConverter = newStructConverter(st, avroType, catalystPath, 
avroPath)
-        val numFields = st.length
-        (getter, ordinal) => structConverter(getter.getStruct(ordinal, 
numFields))
-
-      
////////////////////////////////////////////////////////////////////////////////////////////
-      // Following section is amended to the original (Spark's) implementation
-      // >>> BEGINS
-      
////////////////////////////////////////////////////////////////////////////////////////////
-
-      case (st: StructType, UNION) =>
-        val unionConverter = newUnionConverter(st, avroType, catalystPath, 
avroPath)
-        val numFields = st.length
-        (getter, ordinal) => unionConverter(getter.getStruct(ordinal, 
numFields))
-
-      
////////////////////////////////////////////////////////////////////////////////////////////
-      // <<< ENDS
-      
////////////////////////////////////////////////////////////////////////////////////////////
-
-      case (MapType(kt, vt, valueContainsNull), MAP) if kt == StringType =>
-        val valueConverter = newConverter(
-          vt, resolveNullableType(avroType.getValueType, valueContainsNull),
-          catalystPath :+ "value", avroPath :+ "value")
-        (getter, ordinal) =>
-          val mapData = getter.getMap(ordinal)
-          val len = mapData.numElements()
-          val result = new java.util.HashMap[String, Any](len)
-          val keyArray = mapData.keyArray()
-          val valueArray = mapData.valueArray()
-          var i = 0
-          while (i < len) {
-            val key = keyArray.getUTF8String(i).toString
-            if (valueContainsNull && valueArray.isNullAt(i)) {
-              result.put(key, null)
-            } else {
-              result.put(key, valueConverter(valueArray, i))
-            }
-            i += 1
-          }
-          result
-
-      case (_: YearMonthIntervalType, INT) =>
-        (getter, ordinal) => getter.getInt(ordinal)
-
-      case (_: DayTimeIntervalType, LONG) =>
-        (getter, ordinal) => getter.getLong(ordinal)
-
-      case _ =>
-        throw new IncompatibleSchemaException(errorPrefix +
-          s"schema is incompatible (sqlType = ${catalystType.sql}, avroType = 
$avroType)")
-    }
-  }
-
-  private def newStructConverter(catalystStruct: StructType,
-                                 avroStruct: Schema,
-                                 catalystPath: Seq[String],
-                                 avroPath: Seq[String]): InternalRow => Record 
= {
-
-    val avroSchemaHelper = new AvroUtils.AvroSchemaHelper(
-      avroStruct, catalystStruct, avroPath, catalystPath, positionalFieldMatch)
-
-    avroSchemaHelper.validateNoExtraCatalystFields(ignoreNullable = false)
-    avroSchemaHelper.validateNoExtraRequiredAvroFields()
-
-    val (avroIndices, fieldConverters) = avroSchemaHelper.matchedFields.map {
-      case AvroMatchedField(catalystField, _, avroField) =>
-        val converter = newConverter(catalystField.dataType,
-          resolveNullableType(avroField.schema(), catalystField.nullable),
-          catalystPath :+ catalystField.name, avroPath :+ avroField.name)
-        (avroField.pos(), converter)
-    }.toArray.unzip
-
-    val numFields = catalystStruct.length
-    row: InternalRow =>
-      val result = new Record(avroStruct)
-      var i = 0
-      while (i < numFields) {
-        if (row.isNullAt(i)) {
-          result.put(avroIndices(i), null)
-        } else {
-          result.put(avroIndices(i), fieldConverters(i).apply(row, i))
-        }
-        i += 1
-      }
-      result
-  }
-
-  
////////////////////////////////////////////////////////////////////////////////////////////
-  // Following section is amended to the original (Spark's) implementation
-  // >>> BEGINS
-  
////////////////////////////////////////////////////////////////////////////////////////////
-
-  private def newUnionConverter(catalystStruct: StructType,
-                                avroUnion: Schema,
-                                catalystPath: Seq[String],
-                                avroPath: Seq[String]): InternalRow => Any = {
-    if (avroUnion.getType != UNION || !canMapUnion(catalystStruct, avroUnion)) 
{
-      throw new IncompatibleSchemaException(s"Cannot convert Catalyst type 
$catalystStruct to " +
-        s"Avro type $avroUnion.")
-    }
-    val nullable = avroUnion.getTypes.size() > 0 && 
avroUnion.getTypes.get(0).getType == Type.NULL
-    val avroInnerTypes = if (nullable) {
-      avroUnion.getTypes.asScala.tail
-    } else {
-      avroUnion.getTypes.asScala
-    }
-    val fieldConverters = catalystStruct.zip(avroInnerTypes).map {
-      case (f1, f2) => newConverter(f1.dataType, f2, catalystPath, avroPath)
-    }
-    val numFields = catalystStruct.length
-    (row: InternalRow) =>
-      var i = 0
-      var result: Any = null
-      while (i < numFields) {
-        if (!row.isNullAt(i)) {
-          if (result != null) {
-            throw new IncompatibleSchemaException(s"Cannot convert Catalyst 
record $catalystStruct to " +
-              s"Avro union $avroUnion. Record has more than one optional 
values set")
-          }
-          result = fieldConverters(i).apply(row, i)
-        }
-        i += 1
-      }
-      if (!nullable && result == null) {
-        throw new IncompatibleSchemaException(s"Cannot convert Catalyst record 
$catalystStruct to " +
-          s"Avro union $avroUnion. Record has no values set, while should have 
exactly one")
-      }
-      result
-  }
-
-  private def canMapUnion(catalystStruct: StructType, avroStruct: Schema): 
Boolean = {
-    (avroStruct.getTypes.size() > 0 &&
-      avroStruct.getTypes.get(0).getType == Type.NULL &&
-      avroStruct.getTypes.size() - 1 == catalystStruct.length) || 
avroStruct.getTypes.size() == catalystStruct.length
-  }
-
-  
////////////////////////////////////////////////////////////////////////////////////////////
-  // <<< ENDS
-  
////////////////////////////////////////////////////////////////////////////////////////////
-
-
-  /**
-   * Resolve a possibly nullable Avro Type.
-   *
-   * An Avro type is nullable when it is a [[UNION]] of two types: one null 
type and another
-   * non-null type. This method will check the nullability of the input Avro 
type and return the
-   * non-null type within when it is nullable. Otherwise it will return the 
input Avro type
-   * unchanged. It will throw an [[UnsupportedAvroTypeException]] when the 
input Avro type is an
-   * unsupported nullable type.
-   *
-   * It will also log a warning message if the nullability for Avro and 
catalyst types are
-   * different.
-   */
-  private def resolveNullableType(avroType: Schema, nullable: Boolean): Schema 
= {
-    val (avroNullable, resolvedAvroType) = resolveAvroType(avroType)
-    warnNullabilityDifference(avroNullable, nullable)
-    resolvedAvroType
-  }
-
-  /**
-   * Check the nullability of the input Avro type and resolve it when it is 
nullable. The first
-   * return value is a [[Boolean]] indicating if the input Avro type is 
nullable. The second
-   * return value is the possibly resolved type.
-   */
-  private def resolveAvroType(avroType: Schema): (Boolean, Schema) = {
-    if (avroType.getType == Type.UNION) {
-      val fields = avroType.getTypes.asScala
-      val actualType = fields.filter(_.getType != Type.NULL)
-      if (fields.length == 2 && actualType.length == 1) {
-        (true, actualType.head)
-      } else {
-        // This is just a normal union, not used to designate nullability
-        (false, avroType)
-      }
-    } else {
-      (false, avroType)
-    }
-  }
-
-  /**
-   * log a warning message if the nullability for Avro and catalyst types are 
different.
-   */
-  private def warnNullabilityDifference(avroNullable: Boolean, 
catalystNullable: Boolean): Unit = {
-    if (avroNullable && !catalystNullable) {
-      logWarning("Writing Avro files with nullable Avro schema and 
non-nullable catalyst schema.")
-    }
-    if (!avroNullable && catalystNullable) {
-      logWarning("Writing Avro files with non-nullable Avro schema and 
nullable catalyst " +
-        "schema will throw runtime exception if there is a record with null 
value.")
-    }
-  }
-}
-
-object AvroSerializer {
-
-  // NOTE: Following methods have been renamed in Spark 3.2.1 [1] making 
[[AvroSerializer]] implementation
-  //       (which relies on it) be only compatible with the exact same version 
of [[DataSourceUtils]].
-  //       To make sure this implementation is compatible w/ all Spark 
versions w/in Spark 3.2.x branch,
-  //       we're preemptively cloned those methods to make sure Hudi is 
compatible w/ Spark 3.2.0 as well as
-  //       w/ Spark >= 3.2.1
-  //
-  // [1] https://github.com/apache/spark/pull/34978
-
-  def createDateRebaseFuncInWrite(rebaseMode: LegacyBehaviorPolicy.Value,
-                                  format: String): Int => Int = rebaseMode 
match {
-    case LegacyBehaviorPolicy.EXCEPTION => days: Int =>
-      if (days < RebaseDateTime.lastSwitchGregorianDay) {
-        throw DataSourceUtils.newRebaseExceptionInWrite(format)
-      }
-      days
-    case LegacyBehaviorPolicy.LEGACY => 
RebaseDateTime.rebaseGregorianToJulianDays
-    case LegacyBehaviorPolicy.CORRECTED => identity[Int]
-  }
-
-  def createTimestampRebaseFuncInWrite(rebaseMode: LegacyBehaviorPolicy.Value,
-                                       format: String): Long => Long = 
rebaseMode match {
-    case LegacyBehaviorPolicy.EXCEPTION => micros: Long =>
-      if (micros < RebaseDateTime.lastSwitchGregorianTs) {
-        throw DataSourceUtils.newRebaseExceptionInWrite(format)
-      }
-      micros
-    case LegacyBehaviorPolicy.LEGACY =>
-      val timeZone = SQLConf.get.sessionLocalTimeZone
-      
RebaseDateTime.rebaseGregorianToJulianMicros(TimeZone.getTimeZone(timeZone), _)
-    case LegacyBehaviorPolicy.CORRECTED => identity[Long]
-  }
-
-}

Reply via email to