@@ -25,6 +25,7 @@ import org.apache.spark.sql.delta.actions.{Action, FileAction, Metadata, Protoco
2525import org .apache .spark .sql .delta .schema .SchemaUtils
2626import org .apache .spark .sql .delta .storage .ClosableIterator
2727import org .apache .spark .sql .delta .storage .ClosableIterator ._
28+ import org .apache .spark .sql .delta .v2 .interop .{AbstractMetadata , AbstractProtocol }
2829
2930import org .apache .spark .internal .Logging
3031import org .apache .spark .sql .SparkSession
@@ -89,26 +90,13 @@ import org.apache.spark.sql.types.StructType
8990 */
9091trait DeltaSourceMetadataEvolutionSupport extends DeltaSourceBase { base : DeltaSource =>
9192
92- /**
93- * Whether this DeltaSource is utilizing a schema log entry as its read schema.
94- *
95- * If user explicitly turn on the flag to fall back to using latest schema to read (i.e. the
96- * legacy mode), we will ignore the schema log.
97- */
9893 protected def trackingMetadataChange : Boolean =
99- ! schemaReadOptions.allowUnsafeStreamingReadOnColumnMappingSchemaChanges &&
100- metadataTrackingLog.flatMap(_.getCurrentTrackedMetadata).nonEmpty
94+ DeltaSourceMetadataEvolutionSupport .shouldTrackMetadataChange(
95+ schemaReadOptions, metadataTrackingLog)
10196
102- /**
103- * Whether a schema tracking log is provided (and is empty), so we could initialize eagerly.
104- * This should only be used for the first write to the schema log, after then, schema tracking
105- * should not rely on this state any more.
106- */
10797 protected def readyToInitializeMetadataTrackingEagerly : Boolean =
108- ! schemaReadOptions.allowUnsafeStreamingReadOnColumnMappingSchemaChanges &&
109- metadataTrackingLog.exists { log =>
110- log.getCurrentTrackedMetadata.isEmpty && log.initMetadataLogEagerly
111- }
98+ DeltaSourceMetadataEvolutionSupport .shouldInitializeMetadataTrackingEagerly(
99+ schemaReadOptions, metadataTrackingLog)
112100
113101
114102 /**
@@ -126,44 +114,22 @@ trait DeltaSourceMetadataEvolutionSupport extends DeltaSourceBase { base: DeltaS
126114 }
127115 }
128116
129- /**
130- * Check the table metadata or protocol changed since the initial read snapshot. We make sure:
131- * 1. The schema is the same, except for internal metadata, AND
132- * 2. The delta related table configurations are strictly equal, AND
133- * 3. The incoming metadata change should not be considered a failure-causing change if we have
134- * marked the persisted schema and the stream progress is behind that schema version.
135- * This could happen when we've already merged consecutive schema changes during the analysis
136- * phase and we are using the merged schema as the read schema. All the schema changes in
137- * between can be safely ignored because they won't contribute any data.
138- */
139117 private def hasMetadataOrProtocolChangeComparedToStreamMetadata (
140118 metadataChangeOpt : Option [Metadata ],
141119 protocolChangeOpt : Option [Protocol ],
142120 newSchemaVersion : Long ): Boolean = {
143- if (persistedMetadataAtSourceInit.exists(_.deltaCommitVersion >= newSchemaVersion)) {
144- false
145- } else {
146- protocolChangeOpt.exists(_ != readProtocolAtSourceInit) ||
147- metadataChangeOpt.exists { newMetadata =>
148- hasSchemaChangeComparedToStreamMetadata(newMetadata.schema) ||
149- newMetadata.partitionSchema != readPartitionSchemaAtSourceInit ||
150- newMetadata.configuration.filterKeys(_.startsWith(" delta." )).toMap !=
151- readConfigurationsAtSourceInit.filterKeys(_.startsWith(" delta." )).toMap
152- }
153- }
121+ DeltaSourceMetadataEvolutionSupport .hasMetadataOrProtocolChangeComparedToStreamMetadata(
122+ metadataChangeOpt,
123+ protocolChangeOpt,
124+ newSchemaVersion,
125+ persistedMetadataAtSourceInit,
126+ readProtocolAtSourceInit,
127+ readSchemaAtSourceInit,
128+ readPartitionSchemaAtSourceInit,
129+ readConfigurationsAtSourceInit,
130+ spark)
154131 }
155132
156- /**
157- * Check that the give schema is the same as the schema from the initial read snapshot.
158- */
159- private def hasSchemaChangeComparedToStreamMetadata (newSchema : StructType ): Boolean =
160- if (spark.conf.get(DeltaSQLConf .DELTA_STREAMING_IGNORE_INTERNAL_METADATA_FOR_SCHEMA_CHANGE )) {
161- DeltaTableUtils .removeInternalWriterMetadata(spark, newSchema) !=
162- DeltaTableUtils .removeInternalWriterMetadata(spark, readSchemaAtSourceInit)
163- } else {
164- newSchema != readSchemaAtSourceInit
165- }
166-
167133 /**
168134 * If the current stream metadata is not equal to the metadata change in [[metadataChangeOpt ]],
169135 * return a metadata change barrier [[IndexedFile ]].
@@ -685,6 +651,96 @@ object DeltaSourceMetadataEvolutionSupport extends Logging {
685651 spark.sessionState.conf.getConf(
686652 DeltaSQLConf .DELTA_TYPE_WIDENING_BYPASS_STREAMING_TYPE_CHANGE_CHECK )
687653
654+ /**
655+ * Whether this DeltaSource is utilizing a schema log entry as its read schema.
656+ *
657+ * If user explicitly turn on the flag to fall back to using latest schema to read (i.e. the
658+ * legacy mode), we will ignore the schema log.
659+ */
660+ def shouldTrackMetadataChange (
661+ schemaReadOptions : DeltaStreamUtils .SchemaReadOptions ,
662+ metadataTrackingLog : Option [DeltaSourceMetadataTrackingLog ]): Boolean = {
663+ ! schemaReadOptions.allowUnsafeStreamingReadOnColumnMappingSchemaChanges &&
664+ metadataTrackingLog.flatMap(_.getCurrentTrackedMetadata).nonEmpty
665+ }
666+
667+ /**
668+ * Whether a schema tracking log is provided (and is empty), so we could initialize eagerly.
669+ * This should only be used for the first write to the schema log, after then, schema tracking
670+ * should not rely on this state any more.
671+ */
672+ def shouldInitializeMetadataTrackingEagerly (
673+ schemaReadOptions : DeltaStreamUtils .SchemaReadOptions ,
674+ metadataTrackingLog : Option [DeltaSourceMetadataTrackingLog ]): Boolean = {
675+ ! schemaReadOptions.allowUnsafeStreamingReadOnColumnMappingSchemaChanges &&
676+ metadataTrackingLog.exists { log =>
677+ log.getCurrentTrackedMetadata.isEmpty && log.initMetadataLogEagerly
678+ }
679+ }
680+
681+ /**
682+ * Check the table metadata or protocol changed since the initial read snapshot. We make sure:
683+ * 1. The schema is the same, except for internal metadata, AND
684+ * 2. The delta related table configurations are strictly equal, AND
685+ * 3. The incoming metadata change should not be considered a failure-causing change if we have
686+ * marked the persisted schema and the stream progress is behind that schema version.
687+ * This could happen when we've already merged consecutive schema changes during the analysis
688+ * phase and we are using the merged schema as the read schema. All the schema changes in
689+ * between can be safely ignored because they won't contribute any data.
690+ *
691+ * @param metadataChangeOpt New metadata action, if any.
692+ * @param protocolChangeOpt New protocol action, if any.
693+ * @param newSchemaVersion The version of the incoming change.
694+ * @param persistedMetadataAtSourceInit The persisted metadata at source init, if any.
695+ * @param readProtocolAtSourceInit The protocol at source init.
696+ * @param readSchemaAtSourceInit The schema at source init.
697+ * @param readPartitionSchemaAtSourceInit The partition schema at source init.
698+ * @param readConfigurationsAtSourceInit The table configurations at source init.
699+ * @param spark The SparkSession (used for SQL conf checks).
700+ */
701+ def hasMetadataOrProtocolChangeComparedToStreamMetadata (
702+ metadataChangeOpt : Option [AbstractMetadata ],
703+ protocolChangeOpt : Option [AbstractProtocol ],
704+ newSchemaVersion : Long ,
705+ persistedMetadataAtSourceInit : Option [PersistedMetadata ],
706+ readProtocolAtSourceInit : AbstractProtocol ,
707+ readSchemaAtSourceInit : StructType ,
708+ readPartitionSchemaAtSourceInit : StructType ,
709+ readConfigurationsAtSourceInit : Map [String , String ],
710+ spark : SparkSession ): Boolean = {
711+ if (persistedMetadataAtSourceInit.exists(_.deltaCommitVersion >= newSchemaVersion)) {
712+ false
713+ } else {
714+ protocolChangeOpt.exists(p =>
715+ p.minReaderVersion != readProtocolAtSourceInit.minReaderVersion ||
716+ p.minWriterVersion != readProtocolAtSourceInit.minWriterVersion ||
717+ p.readerFeatures != readProtocolAtSourceInit.readerFeatures ||
718+ p.writerFeatures != readProtocolAtSourceInit.writerFeatures) ||
719+ metadataChangeOpt.exists { newMetadata =>
720+ hasSchemaChangeComparedToStreamMetadata(
721+ newMetadata.schema, readSchemaAtSourceInit, spark) ||
722+ newMetadata.partitionSchema != readPartitionSchemaAtSourceInit ||
723+ newMetadata.configuration.filterKeys(_.startsWith(" delta." )).toMap !=
724+ readConfigurationsAtSourceInit.filterKeys(_.startsWith(" delta." )).toMap
725+ }
726+ }
727+ }
728+
729+ /**
730+ * Check that the given schema is the same as the schema from the initial read snapshot.
731+ */
732+ def hasSchemaChangeComparedToStreamMetadata (
733+ newSchema : StructType ,
734+ readSchemaAtSourceInit : StructType ,
735+ spark : SparkSession ): Boolean = {
736+ if (spark.conf.get(DeltaSQLConf .DELTA_STREAMING_IGNORE_INTERNAL_METADATA_FOR_SCHEMA_CHANGE )) {
737+ DeltaTableUtils .removeInternalWriterMetadata(spark, newSchema) !=
738+ DeltaTableUtils .removeInternalWriterMetadata(spark, readSchemaAtSourceInit)
739+ } else {
740+ newSchema != readSchemaAtSourceInit
741+ }
742+ }
743+
688744 /**
689745 * Speculate ahead and find the next merged consecutive metadata change if possible.
690746 * A metadata change is either:
0 commit comments