github-actions[bot] commented on code in PR #69536:
URL: https://github.com/apache/doris/pull/69536#discussion_r4363903193
##########
fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java:
##########
@@ -2327,8 +2341,40 @@ private static List<Schema> metadataSchemaHistory(Table
table) {
return new ArrayList<>(table.schemas().values());
}
- private static void addHistoricalEqualityFields(
- List<NestedField> fields, Set<Integer> missingFieldIds, Schema
historicalSchema) {
+ /**
+ * Field IDs are never reused and a field's type only changes by
promotion, which is monotonic, so the widest
+ * type a field has in any retained schema covers every file written for
it.
+ */
+ private static Map<Integer, Type> widestPromotedTypes(Set<Integer>
fieldIds, List<Schema> schemas) {
+ Map<Integer, Type> widestTypes = new HashMap<>();
+ for (Schema schema : schemas) {
+ for (Integer fieldId : fieldIds) {
+ Type type = schema.findType(fieldId);
+ if (type == null) {
+ continue;
+ }
+ Type widest = widestTypes.get(fieldId);
+ if (widest == null || TypeUtil.isPromotionAllowed(widest,
type.asPrimitiveType())) {
Review Comment:
[P1] Preserve v3 temporal promotions when selecting the comparison type
Iceberg v3 permits DATE to unzoned TIMESTAMP/TIMESTAMP_NS promotion, but the
pinned 1.12.0 `TypeUtil.isPromotionAllowed` only recognizes INT/LONG,
FLOAT/DOUBLE and decimal precision
([spec](https://github.com/apache/iceberg/blob/apache-iceberg-1.12.0/format/spec.md#schema-evolution),
[helper](https://github.com/apache/iceberg/blob/apache-iceberg-1.12.0/api/src/main/java/org/apache/iceberg/types/TypeUtil.java)).
Iterating retained schemas oldest-first therefore keeps DATE, and
`withWidestTypes` replaces even a correctly selected TIMESTAMP lineage field
with DATE.
For an unpartitioned v3 table, retain a row with `k=DATE '2024-01-02'`,
promote k to TIMESTAMP, commit an equality delete for `2024-01-02 12:00:00`,
then drop k. Omit column metrics so the delete reaches the reader. V2
`_resolve_equality_delete_fields` chooses carrier DATE and
`_load_equality_delete_file` casts the noon tombstone to DATE: it incorrectly
deletes the original row, whose promoted value is midnight. The previous
history lookup retained TIMESTAMP. Handle the v3 temporal comparison domains
explicitly, avoid narrowing the lineage type, and cover this sequence. This
differs from the existing INT/LONG thread because the promoted schema is
present on the lineage.
##########
fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java:
##########
@@ -2280,38 +2280,52 @@ 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. A
dropped key nevertheless takes the
+ * widest type it has in any retained schema: a delete committed after a
schema-only promotion holds values of
+ * the promoted type even when no snapshot records it. 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--) {
- addHistoricalEqualityFields(fields, missing,
metadataSchemas.get(index));
+ List<Schema> metadataSchemas = metadataSchemaHistory(table);
+ Map<Integer, Type> widestTypes = widestPromotedTypes(missing,
metadataSchemas);
+ // 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,
widestTypes);
Review Comment:
[P2] Retain schema-only optionality when recovering a dropped key
This consumes a key from `missing` using its snapshot-lineage definition
before the metadata fallback can observe a compatible required-to-optional
change. Consider a v2 unpartitioned table with required `k INT` and no initial
default: append S1, make k optional without a snapshot, prepare a NULL
equality-delete `RowDelta`, drop k, append an id-only file as S2, then commit
the pending delete as S3. The SDK allows this pending commit and stamps the
current dropped schema
([SnapshotProducer](https://github.com/apache/iceberg/blob/apache-iceberg-1.12.0/core/src/main/java/org/apache/iceberg/SnapshotProducer.java),
[BaseRowDelta](https://github.com/apache/iceberg/blob/apache-iceberg-1.12.0/core/src/main/java/org/apache/iceberg/BaseRowDelta.java)).
Use files without column metrics so pruning does not exclude the delete.
S2/S3 omit k; recovery reaches S1 and restores REQUIRED, while the previous
metadata lookup selected the intervening OPTIONAL definition. BE then rejects
S2 with `Missing required field: k` instead of materializing NULL for the
equality comparison. Type widening does not address this unchanged-INT case.
Preserve the compatible schema-only optionality transition and add this
pending-delete/drop/append test; globally relaxing still-required fields would
hide real errors.
--
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]