morningman commented on code in PR #69536:
URL: https://github.com/apache/doris/pull/69536#discussion_r4363772474
##########
fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java:
##########
@@ -2280,37 +2280,48 @@ private Set<Integer>
cachedApplicableEqualityDeleteFieldIds(Table table, TableSc
* delete manifests does not enumerate data files or byte-split tasks, and
retained state is bounded by the
* number of equality keys rather than the table's entire schema history.
*
- * <p>The metadata schema list is searched in reverse chronology up to the
selected schema (schema IDs are
- * identifiers, not a sequence), so the latest definition of each stable
field ID wins. Current fields remain
- * first, allowing a dropped/re-added name to resolve the projected field
by name while a live historical
- * equality key resolves by its stable field ID.</p>
+ * <p>Schema IDs are identifiers, not a sequence: Iceberg reuses an ID
when evolution returns to an earlier
+ * schema, so a schema's position in the metadata list does not tell when
it was last active, and the list may
+ * also hold schemas committed after a time-travel or branch target. The
scan snapshot's parent chain is
+ * therefore searched first, so the definition active on that lineage
wins; the whole metadata list, newest
+ * first, is the fallback for schema-only changes and expired ancestors.
Current fields remain first, allowing
+ * a dropped/re-added name to resolve the projected field by name while a
live historical equality key
+ * resolves by its stable field ID.</p>
*/
@VisibleForTesting
static List<NestedField> schemaForPotentialEqualityDeletes(
Table table, TableScan scan, Schema scanSchema) {
return schemaForPotentialEqualityDeletes(
- table, scanSchema, applicableEqualityDeleteFieldIds(table,
scan));
+ table, scan.snapshot(), scanSchema,
applicableEqualityDeleteFieldIds(table, scan));
}
- private static List<NestedField> schemaForPotentialEqualityDeletes(
- Table table, Schema scanSchema, Set<Integer> equalityFieldIds) {
- List<Schema> metadataSchemas = metadataSchemaHistory(table);
- int selectedSchemaIndex = -1;
- for (int index = 0; index < metadataSchemas.size(); index++) {
- if (metadataSchemas.get(index).schemaId() ==
scanSchema.schemaId()) {
- selectedSchemaIndex = index;
- }
- }
- int lastRelevantIndex = selectedSchemaIndex >= 0
- ? selectedSchemaIndex : metadataSchemas.size() - 1;
+ @VisibleForTesting
+ static List<NestedField> schemaForPotentialEqualityDeletes(
+ Table table, Snapshot scanSnapshot, Schema scanSchema,
Set<Integer> equalityFieldIds) {
Set<Integer> missing = new HashSet<>(equalityFieldIds);
missing.removeAll(TypeUtil.indexById(scanSchema.asStruct()).keySet());
if (missing.isEmpty()) {
return scanSchema.columns();
}
List<NestedField> fields = new ArrayList<>(scanSchema.columns());
- for (int index = lastRelevantIndex; index >= 0 && !missing.isEmpty();
index--) {
+ // Snapshots on one lineage mostly share a schema, so each schema is
indexed at most once.
+ Set<Integer> lineageSchemaIds = new HashSet<>();
+ for (Snapshot snapshot = scanSnapshot; snapshot != null &&
!missing.isEmpty();
+ snapshot = snapshot.parentId() == null ? null :
table.snapshot(snapshot.parentId())) {
+ Integer schemaId = snapshot.schemaId();
+ if (schemaId == null || !lineageSchemaIds.add(schemaId)) {
+ continue;
+ }
+ Schema lineageSchema = table.schemas().get(schemaId);
+ if (lineageSchema == null) {
+ throw new IllegalStateException(
+ "Iceberg snapshot schema " + schemaId + " is absent
from table metadata");
+ }
+ addHistoricalEqualityFields(fields, missing, lineageSchema);
Review Comment:
Confirmed, and fixed in 71f51edf754. With Iceberg 1.12 a pending `RowDelta`
is stamped with the schema that is current when it commits
(`SnapshotProducer.apply` → `base.currentSchemaId()`), and nothing validates
its equality field IDs against that schema, so a key promoted by a schema-only
change and dropped before the delete commits has no snapshot on the lineage
that records the promoted type. FileScannerV2 compares equality keys in the
carrier's type, so the INT carrier narrowed the LONG tombstone to NULL
(non-strict) and matched NULL keys; the V1 reader compares in the delete file's
own type and was not affected.
The carrier now keeps the lineage definition of a dropped key (name,
optionality, initial default) but takes the widest type its field ID has in any
retained schema, where wider means an Iceberg promotion
(`TypeUtil.isPromotionAllowed`: INT→LONG, FLOAT→DOUBLE, decimal precision).
Field IDs are never reused and promotion is monotonic and lossless, so that
type covers every file written for the field, and widening is harmless when it
comes from a schema after a time-travel target.
`equalityCarrierKeepsPromotedTypeOfKeyDroppedBeforeItsDeleteCommits` replays
the sequence with the SDK (promote, prepare the delete, drop, commit) and fails
without the widening (`expected: <long> but was: <int>`).
branch-4.2 resolves keys the same way
(`IcebergScanNode.getSchemaFieldsForScan`) and has the same FileScannerV2
comparison, so it carries the same defect; it will be passed to the 4.x
maintainers. One residual case is shared by every version:
`expireSnapshots().cleanExpiredMetadata(true)` can remove a schema-only schema
that no retained snapshot references, after which no metadata records the
promoted type; closing that needs a BE-side guard when a delete file's key type
is wider than the carrier, which is outside this PR.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]