-
Notifications
You must be signed in to change notification settings - Fork 232
[AURON #2434] Support full-data-file Iceberg changelog deletes #2435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,7 @@ import scala.collection.JavaConverters._ | |
| import scala.util.control.NonFatal | ||
|
|
||
| import org.apache.commons.lang3.reflect.MethodUtils | ||
| import org.apache.iceberg.{AddedRowsScanTask, ChangelogOperation, ChangelogScanTask, FileFormat, FileScanTask, MetadataColumns, ScanTask} | ||
| import org.apache.iceberg.{AddedRowsScanTask, ChangelogOperation, ChangelogScanTask, DataFile, DeletedDataFileScanTask, FileFormat, FileScanTask, MetadataColumns, ScanTask} | ||
| import org.apache.iceberg.expressions.{And => IcebergAnd, BoundPredicate, Expression => IcebergExpression, Not => IcebergNot, Or => IcebergOr, UnboundPredicate} | ||
| import org.apache.iceberg.spark.source.AuronIcebergSourceUtil | ||
| import org.apache.spark.internal.Logging | ||
|
|
@@ -267,22 +267,14 @@ object IcebergScanSupport extends Logging { | |
| return None | ||
| } | ||
|
|
||
| val addedRowsTasks = changelogTasks.collect { case task: AddedRowsScanTask => task } | ||
| // First native changelog support is insert-only. Delete and update images need Iceberg | ||
| // delete-file handling, so keep them on Spark's reader for now. | ||
| if (addedRowsTasks.size != changelogTasks.size) { | ||
| // Native changelog scan can read data-file rows directly for insert tasks and | ||
| // full-data-file delete tasks. Row-level deletes still need Iceberg delete-file handling. | ||
| val nativeChangelogTasks = changelogTasks.flatMap(toNativeChangelogDataFileTask) | ||
| if (nativeChangelogTasks.size != changelogTasks.size) { | ||
| return None | ||
| } | ||
|
|
||
| if (!addedRowsTasks.forall(_.operation() == ChangelogOperation.INSERT)) { | ||
| return None | ||
| } | ||
|
|
||
| if (!addedRowsTasks.forall(task => deletesEmpty(task.deletes()))) { | ||
| return None | ||
| } | ||
|
|
||
| val formats = addedRowsTasks.map(_.file().format()).distinct | ||
| val formats = nativeChangelogTasks.map(_.file.format()).distinct | ||
| if (formats.size > 1) { | ||
| return None | ||
| } | ||
|
|
@@ -298,7 +290,7 @@ object IcebergScanSupport extends Logging { | |
| } | ||
|
|
||
| val pruningPredicates = collectPruningPredicates(scan.asInstanceOf[AnyRef], readSchema) | ||
| val nativeTasks = addedRowsTasks.map(task => toNativeScanTask(task, partitionSchema)) | ||
| val nativeTasks = nativeChangelogTasks.map(task => toNativeScanTask(task, partitionSchema)) | ||
| Some( | ||
| IcebergScanPlan( | ||
| nativeTasks, | ||
|
|
@@ -528,6 +520,12 @@ object IcebergScanSupport extends Logging { | |
|
|
||
| private case class IcebergPartitionView(tasks: Seq[ScanTask]) | ||
|
|
||
| private case class NativeChangelogDataFileTask( | ||
| file: DataFile, | ||
| start: Long, | ||
| length: Long, | ||
| changelogTask: ChangelogScanTask) | ||
|
|
||
| private def icebergPartition(partition: InputPartition): Option[IcebergPartitionView] = { | ||
| val className = partition.getClass.getName | ||
| // Only accept Iceberg SparkInputPartition to access task groups. | ||
|
|
@@ -559,6 +557,23 @@ object IcebergScanSupport extends Logging { | |
| } | ||
| } | ||
|
|
||
| private def toNativeChangelogDataFileTask( | ||
| task: ChangelogScanTask): Option[NativeChangelogDataFileTask] = { | ||
| task match { | ||
| case added: AddedRowsScanTask | ||
| if added.operation() == ChangelogOperation.INSERT && | ||
| deletesEmpty(added.deletes()) => | ||
| Some(NativeChangelogDataFileTask(added.file(), added.start(), added.length(), added)) | ||
| case deleted: DeletedDataFileScanTask | ||
| if deleted.operation() == ChangelogOperation.DELETE && | ||
| deletesEmpty(deleted.existingDeletes()) => | ||
| Some( | ||
| NativeChangelogDataFileTask(deleted.file(), deleted.start(), deleted.length(), deleted)) | ||
| case _ => | ||
| None | ||
| } | ||
| } | ||
|
Comment on lines
+560
to
+573
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No change requested, just evidence on this one. The changelog task type that would actually be unsafe to run natively is public interface DeletedRowsScanTask extends ChangelogScanTask, ContentScanTask<DataFile> {
List<DeleteFile> addedDeletes();
List<DeleteFile> existingDeletes();
@Override
default ChangelogOperation operation() {
return ChangelogOperation.DELETE;
}
}It extends For the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, that matches my reading. The native path is intentionally limited by the concrete task type here. DeletedRowsScanTask is a separate ChangelogScanTask implementation, not a DeletedDataFileScanTask, so it will not match this case and will continue to fall back. Keeping or removing the operation() == DELETE check does not affect that, since DeletedRowsScanTask also hard-codes operation() to DELETE. For _change_type, I agree the value should come from the task operation itself. The metadata materialization path already uses requiredChangelogTask(name).operation().name(), so if Iceberg ever provides a supported changelog task with a different operation value, the metadata column will reflect the task value rather than the Scala match type. No additional code change from my side. |
||
|
|
||
| private def toNativeScanTask( | ||
| task: FileScanTask, | ||
| partitionSchema: StructType): IcebergNativeScanTask = { | ||
|
|
@@ -572,15 +587,18 @@ object IcebergScanSupport extends Logging { | |
| } | ||
|
|
||
| private def toNativeScanTask( | ||
| task: AddedRowsScanTask, | ||
| task: NativeChangelogDataFileTask, | ||
| partitionSchema: StructType): IcebergNativeScanTask = { | ||
| val file = task.file() | ||
| IcebergNativeScanTask( | ||
| file.location(), | ||
| task.start(), | ||
| task.length(), | ||
| file.fileSizeInBytes(), | ||
| metadataPartitionValues(file.location(), file.specId(), Some(task), partitionSchema)) | ||
| task.file.location(), | ||
| task.start, | ||
| task.length, | ||
| task.file.fileSizeInBytes(), | ||
| metadataPartitionValues( | ||
| task.file.location(), | ||
| task.file.specId(), | ||
| Some(task.changelogTask), | ||
| partitionSchema)) | ||
| } | ||
|
|
||
| private def metadataPartitionValues( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.