@@ -25,6 +25,7 @@ import org.apache.spark.sql.delta.actions.{Metadata, Protocol}
2525import org .apache .spark .sql .delta .commands .cdc .CDCReader
2626import org .apache .spark .sql .delta .metering .DeltaLogging
2727import org .apache .spark .sql .delta .schema .{SchemaMergingUtils , SchemaUtils }
28+ import org .apache .spark .sql .delta .v2 .interop .AbstractMetadata
2829import org .apache .spark .sql .delta .sources .DeltaSQLConf
2930import org .json4s .DefaultFormats
3031import org .json4s .jackson .JsonMethods ._
@@ -425,32 +426,34 @@ trait DeltaColumnMappingBase extends DeltaLogging {
425426 }
426427
427428 /**
428- * For each column/field in a Metadata's schema, assign id using the current maximum id
429- * as the basis and increment from there, and assign physical name using UUID
430- * @param newMetadata The new metadata to assign Ids and physical names
431- * @param oldMetadata The old metadata
432- * @param isChangingModeOnExistingTable whether this is part of a commit that changes the
433- * mapping mode on a existing table
434- * @return new metadata with Ids and physical names assigned
429+ * Core logic for assigning column IDs and physical names to a schema.
430+ * Takes [[ AbstractMetadata ]] (no v1 Metadata dependency) so it can be reused by both v1
431+ * and v2 connectors. Bundling schema + configuration on each side avoids the swap footgun
432+ * of having two `StructType` and two `Map` parameters next to each other.
433+ *
434+ * @return (upgradedSchema, maxColumnId) - the schema with IDs/physical names assigned,
435+ * and the final max column ID.
435436 */
436- def assignColumnIdAndPhysicalName (
437- newMetadata : Metadata ,
438- oldMetadata : Metadata ,
437+ private [delta] def assignColumnIdAndPhysicalNameToSchema (
438+ newMetadata : AbstractMetadata ,
439+ oldMetadata : AbstractMetadata ,
439440 isChangingModeOnExistingTable : Boolean ,
440- isOverwritingSchema : Boolean ): Metadata = {
441- val rawSchema = newMetadata.schema
442- var maxId = DeltaConfigs .COLUMN_MAPPING_MAX_ID .fromMetaData(newMetadata) max
443- DeltaConfigs .COLUMN_MAPPING_MAX_ID .fromMetaData(oldMetadata) max
444- findMaxColumnId(rawSchema)
445- val startId = maxId
446- val newSchema =
447- SchemaMergingUtils .transformColumns(rawSchema)((path, field, _) => {
441+ isOverwritingSchema : Boolean ): (StructType , Long ) = {
442+ val newSchema = newMetadata.schema
443+ val oldSchema = oldMetadata.schema
444+ val newConfiguration = newMetadata.configuration
445+ val oldConfiguration = oldMetadata.configuration
446+ var maxId = DeltaConfigs .COLUMN_MAPPING_MAX_ID .fromMap(newConfiguration) max
447+ DeltaConfigs .COLUMN_MAPPING_MAX_ID .fromMap(oldConfiguration) max
448+ findMaxColumnId(newSchema)
449+ val resultSchema =
450+ SchemaMergingUtils .transformColumns(newSchema)((path, field, _) => {
448451 val builder = new MetadataBuilder ().withMetadata(field.metadata)
449452
450453 lazy val fullName = path :+ field.name
451454 lazy val existingFieldOpt =
452455 SchemaUtils .findNestedFieldIgnoreCase(
453- oldMetadata.schema , fullName, includeCollections = true )
456+ oldSchema , fullName, includeCollections = true )
454457 lazy val canReuseColumnMappingMetadataDuringOverwrite = {
455458 val canReuse =
456459 isOverwritingSchema &&
@@ -484,12 +487,12 @@ trait DeltaColumnMappingBase extends DeltaLogging {
484487 if (! hasPhysicalName(field)) {
485488 val physicalName = if (isChangingModeOnExistingTable) {
486489 if (existingFieldOpt.isEmpty) {
487- if (oldMetadata.schema .isEmpty) {
490+ if (oldSchema .isEmpty) {
488491 // We should relax the check for tables that have both an empty schema
489492 // and no data. Assumption: no schema => no data
490493 generatePhysicalName
491494 } else throw DeltaErrors .schemaChangeDuringMappingModeChangeNotSupported(
492- oldMetadata.schema, newMetadata.schema )
495+ oldSchema, newSchema )
493496 } else {
494497 // When changing from NoMapping to NameMapping mode, we directly use old display names
495498 // as physical names. This is by design: 1) We don't need to rewrite the
@@ -509,13 +512,30 @@ trait DeltaColumnMappingBase extends DeltaLogging {
509512 }
510513 field.copy(metadata = builder.build())
511514 })
512-
513515 // Starting from IcebergCompatV2, we require writing field-id for List/Map nested fields
514- val (finalSchema, newMaxId) = if (IcebergCompat .isGeqEnabled(newMetadata, 2 )) {
515- rewriteFieldIdsForIceberg(newSchema , maxId)
516+ if (IcebergCompat .anyEnabled(newConfiguration).exists(_.version >= 2 )) {
517+ rewriteFieldIdsForIceberg(resultSchema , maxId)
516518 } else {
517- (newSchema , maxId)
519+ (resultSchema , maxId)
518520 }
521+ }
522+
523+ /**
524+ * For each column/field in a Metadata's schema, assign id using the current maximum id
525+ * as the basis and increment from there, and assign physical name using UUID
526+ * @param newMetadata The new metadata to assign Ids and physical names
527+ * @param oldMetadata The old metadata
528+ * @param isChangingModeOnExistingTable whether this is part of a commit that changes the
529+ * mapping mode on a existing table
530+ * @return new metadata with Ids and physical names assigned
531+ */
532+ def assignColumnIdAndPhysicalName (
533+ newMetadata : Metadata ,
534+ oldMetadata : Metadata ,
535+ isChangingModeOnExistingTable : Boolean ,
536+ isOverwritingSchema : Boolean ): Metadata = {
537+ val (finalSchema, newMaxId) = assignColumnIdAndPhysicalNameToSchema(
538+ newMetadata, oldMetadata, isChangingModeOnExistingTable, isOverwritingSchema)
519539
520540 newMetadata.copy(
521541 schemaString = finalSchema.json,
@@ -775,10 +795,12 @@ trait DeltaColumnMappingBase extends DeltaLogging {
775795 * As of now, `newMetadata` is column mapping read compatible with `oldMetadata` if
776796 * no rename column or drop column has happened in-between.
777797 */
778- def hasNoColumnMappingSchemaChanges (newMetadata : Metadata , oldMetadata : Metadata ,
798+ def hasNoColumnMappingSchemaChanges (
799+ newMetadata : AbstractMetadata ,
800+ oldMetadata : AbstractMetadata ,
779801 allowUnsafeReadOnPartitionChanges : Boolean = false ): Boolean = {
780- def hasColMappingOrPartitionSchemaChangeByMetadata (newMetadata : Metadata ,
781- oldMetadata : Metadata ): Boolean = {
802+ def hasColMappingOrPartitionSchemaChangeByMetadata (
803+ newMetadata : AbstractMetadata , oldMetadata : AbstractMetadata ): Boolean = {
782804 val isBothColumnMappingEnabled =
783805 newMetadata.columnMappingMode != NoMapping && oldMetadata.columnMappingMode != NoMapping
784806 hasColMappingOrPartitionSchemaChange(
@@ -802,15 +824,22 @@ trait DeltaColumnMappingBase extends DeltaLogging {
802824 // the new metadata, as the upgrade would use the logical name as the physical name, we could
803825 // easily capture any difference in the schema using the same is{Drop,Rename}ColumnOperation
804826 // utils.
805- var upgradedMetadata = assignColumnIdAndPhysicalName(
806- oldMetadata, oldMetadata, isChangingModeOnExistingTable = true , isOverwritingSchema = false
807- )
808- // need to change to a column mapping mode too so the utils below can recognize
809- upgradedMetadata = upgradedMetadata.copy(
810- configuration = upgradedMetadata.configuration ++
811- Map (DeltaConfigs .COLUMN_MAPPING_MODE .key -> newMetadata.columnMappingMode.name)
812- )
813- // use the same check
827+ val (upgradedSchema, upgradedMaxId) = assignColumnIdAndPhysicalNameToSchema(
828+ newMetadata = oldMetadata, oldMetadata = oldMetadata,
829+ isChangingModeOnExistingTable = true , isOverwritingSchema = false )
830+ // Construct an AbstractMetadata with the upgraded schema and the new column mapping mode
831+ // so the comparison utils below can recognize column mapping metadata.
832+ val upgradedMetadata = new AbstractMetadata {
833+ val id : String = oldMetadata.id
834+ val name : String = oldMetadata.name
835+ val description : String = oldMetadata.description
836+ val schema : StructType = upgradedSchema
837+ val partitionColumns : Seq [String ] = oldMetadata.partitionColumns
838+ val configuration : Map [String , String ] = oldMetadata.configuration +
839+ (DeltaConfigs .COLUMN_MAPPING_MODE .key -> newMetadata.columnMappingMode.name,
840+ DeltaConfigs .COLUMN_MAPPING_MAX_ID .key -> upgradedMaxId.toString)
841+ val columnMappingMode : DeltaColumnMappingMode = newMetadata.columnMappingMode
842+ }
814843 ! hasColMappingOrPartitionSchemaChangeByMetadata(newMetadata, upgradedMetadata)
815844 } else {
816845 // Prohibit reading across a downgrade.
0 commit comments