This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new dfac7bd3b44 [fix](iceberg) Report historical schema scans to the 
table's metrics reporter (#68965)
dfac7bd3b44 is described below

commit dfac7bd3b44546166580bf13e660daeea8977e7c
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Tue Oct 13 14:54:07 2026 +0800

    [fix](iceberg) Report historical schema scans to the table's metrics 
reporter (#68965)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #67479, #67687
    
    Problem Summary:
    
    **In short.** On master, a scan of an Iceberg snapshot or tag taken
    before a column rename or drop is not reported to the table's metrics
    reporter, so a REST catalog with metrics reporting receives no
    ScanReport for it. This PR makes such scans inherit the table's scan
    context, as branch-4.x does since #67479. Query results do not change.
    
    **Background.** Iceberg reports each scan's metrics (planning time,
    manifests and files scanned) through the `MetricsReporter` in the scan's
    context. `BaseTable.newScan()` puts the table's reporter into that
    context, and catalogs such as REST forward the reports to their server.
    To read a historical schema correctly, master builds the scan with
    `SchemaAwareDataTableScan` (#67687, the master port of #67479), which
    binds the partition specs to the snapshot's schema; branch-4.x does the
    same through `DorisDataTableScan.wrap(table.newScan())`, which keeps the
    context of the table's scan.
    
    **The problem, and what it cost.**
    
    | Scan | master | with this PR |
    |---|---|---|
    | Current schema | reported to the table's reporter | unchanged |
    | Historical schema (a snapshot or tag from before a rename or drop) |
    built from `TableScanContext.empty()`, whose reporter is
    `LoggingMetricsReporter`: the catalog's reporter gets nothing, and the
    FE log gets a "Received metrics report" INFO line | reported to the
    table's reporter |
    
    **How this PR fixes it.** `SchemaAwareDataTableScan.newScan()` takes the
    context of `table.newScan()`, which carries the table's reporter, and
    binds the partition specs as before. A catalog-specific table whose own
    scan is not a `DataTableScan` keeps the plain context it had.
    
    **Results.** The new unit test puts a recording reporter on an in-memory
    table: a current-schema scan and a historical-schema scan each send one
    ScanReport, the second with the snapshot's id and schema id. Without the
    fix the historical scan sends none. All fe-connector-iceberg tests pass
    (1525).
    
    Co-authored-by: Gabriel <[email protected]>
---
 .../apache/iceberg/SchemaAwareDataTableScan.java   |  8 ++++-
 .../iceberg/IcebergScanPlanProviderTest.java       | 40 ++++++++++++++++++++++
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git 
a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/iceberg/SchemaAwareDataTableScan.java
 
b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/iceberg/SchemaAwareDataTableScan.java
index 31ec1e3ea68..50d7849942a 100644
--- 
a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/iceberg/SchemaAwareDataTableScan.java
+++ 
b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/iceberg/SchemaAwareDataTableScan.java
@@ -29,7 +29,13 @@ public final class SchemaAwareDataTableScan extends 
DataTableScan {
     }
 
     public static TableScan newScan(Table table) {
-        return new SchemaAwareDataTableScan(table, table.schema(), 
TableScanContext.empty());
+        // Inherit the context of the table's own scan: it carries the table's 
metrics reporter (for example a REST
+        // catalog's scan reports), so a historical scan reports like a 
current one. A catalog-specific Table may
+        // return its own scan type; such a table keeps the plain context it 
had before.
+        TableScan tableScan = table.newScan();
+        TableScanContext context = tableScan instanceof DataTableScan
+                ? ((DataTableScan) tableScan).context() : 
TableScanContext.empty();
+        return new SchemaAwareDataTableScan(table, table.schema(), context);
     }
 
     /** Returns the selected snapshot's specs bound to the scan schema. */
diff --git 
a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergScanPlanProviderTest.java
 
b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergScanPlanProviderTest.java
index f0d616d7431..3366a38ac88 100644
--- 
a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergScanPlanProviderTest.java
+++ 
b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergScanPlanProviderTest.java
@@ -47,6 +47,7 @@ import org.apache.doris.thrift.schema.external.TFieldPtr;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import org.apache.hadoop.conf.Configuration;
+import org.apache.iceberg.BaseTable;
 import org.apache.iceberg.DataFile;
 import org.apache.iceberg.DataFiles;
 import org.apache.iceberg.DeleteFile;
@@ -76,6 +77,7 @@ import org.apache.iceberg.io.InputFile;
 import org.apache.iceberg.io.OutputFile;
 import org.apache.iceberg.io.StorageCredential;
 import org.apache.iceberg.io.SupportsStorageCredentials;
+import org.apache.iceberg.metrics.ScanReport;
 import org.apache.iceberg.types.Conversions;
 import org.apache.iceberg.types.Types;
 import org.apache.iceberg.types.Types.NestedField;
@@ -2265,6 +2267,44 @@ public class IcebergScanPlanProviderTest {
         assertHistoricalPredicatePlansAfterSchemaEvolution(false, true);
     }
 
+    @Test
+    public void historicalSchemaScanReportsToTheTableMetricsReporter() throws 
IOException {
+        Schema schema = new Schema(
+                Types.NestedField.optional(1, "x", Types.IntegerType.get()),
+                Types.NestedField.optional(2, "y", Types.IntegerType.get()));
+        Table table = createTable("historical_metrics_reporter", schema, 
PartitionSpec.unpartitioned(),
+                Collections.singletonMap(TableProperties.FORMAT_VERSION, "2"));
+        table.newFastAppend().appendFile(dataFile(table.spec(), 
"s3://b/db/historical.parquet",
+                1024, null, null)).commit();
+        long snapshotId = table.currentSnapshot().snapshotId();
+        int schemaId = table.currentSnapshot().schemaId();
+        table.updateSchema().renameColumn("x", "renamed_x").commit();
+        List<ScanReport> reports = new ArrayList<>();
+        ((BaseTable) table).combineMetricsReporter(report -> {
+            if (report instanceof ScanReport) {
+                reports.add((ScanReport) report);
+            }
+        });
+        IcebergScanPlanProvider provider = providerOver(table);
+
+        // Control: a scan of the current schema goes through table.newScan() 
and reports to the table's reporter.
+        provider.planScan(emptySession(), ConnectorScanRequest.builder(
+                new IcebergTableHandle("db1", "t1"), 
Collections.emptyList()).build());
+        Assertions.assertEquals(1, reports.size());
+
+        // WHY: a scan of a historical schema is built by 
SchemaAwareDataTableScan instead of table.newScan(), and it
+        // must keep the table's scan context, or a catalog's metrics reporter 
(for example a REST catalog's scan
+        // reports) misses the scans of an old snapshot or tag. branch-4.x 
DorisDataTableScan.wrap() kept it (#67479).
+        // MUTATION: building the historical scan from an empty context -> no 
second report -> red.
+        IcebergTableHandle historical = new IcebergTableHandle("db1", 
"t1").withSnapshot(snapshotId, null, schemaId);
+        List<ConnectorScanRange> ranges = provider.planScan(emptySession(), 
ConnectorScanRequest.builder(
+                historical, 
Collections.emptyList()).filter(Optional.of(eqInt("x", 1))).build());
+        Assertions.assertEquals(1, ranges.size());
+        Assertions.assertEquals(2, reports.size());
+        Assertions.assertEquals(snapshotId, reports.get(1).snapshotId());
+        Assertions.assertEquals(schemaId, reports.get(1).schemaId());
+    }
+
     private void assertHistoricalPredicatePlansAfterSchemaEvolution(boolean 
dropColumn, boolean evolveSpec)
             throws IOException {
         for (boolean partitioned : new boolean[] {false, true}) {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to