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

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new c735aeb74 fix(compaction): close the planning scan in Analyze (#2261)
c735aeb74 is described below

commit c735aeb74c5d6c197047dfa71eeba9a0d1fae0bd
Author: Revanth <[email protected]>
AuthorDate: Wed Oct 21 16:13:11 2026 -0500

    fix(compaction): close the planning scan in Analyze (#2261)
---
 table/compaction/analyze.go      |  9 ++++++++-
 table/compaction/analyze_test.go | 41 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+), 1 deletion(-)

diff --git a/table/compaction/analyze.go b/table/compaction/analyze.go
index 9513836a3..f45dd1802 100644
--- a/table/compaction/analyze.go
+++ b/table/compaction/analyze.go
@@ -26,8 +26,15 @@ import (
 
 // Analyze scans the table and produces a compaction Plan without
 // modifying anything. This is the dry-run / planning entry point.
+//
+// Analyze closes its scan before returning, which releases a remote plan's
+// resources. Callers read the returned tasks through a scan of their own, as
+// table.ExecuteCompactionGroup does.
 func Analyze(ctx context.Context, tbl *table.Table, cfg Config) (Plan, error) {
-       tasks, err := tbl.Scan().PlanFiles(ctx)
+       scan := tbl.Scan()
+       defer scan.Close()
+
+       tasks, err := scan.PlanFiles(ctx)
        if err != nil {
                return Plan{}, fmt.Errorf("plan files for compaction analysis: 
%w", err)
        }
diff --git a/table/compaction/analyze_test.go b/table/compaction/analyze_test.go
index 3609b91eb..2b51582c3 100644
--- a/table/compaction/analyze_test.go
+++ b/table/compaction/analyze_test.go
@@ -186,3 +186,44 @@ func TestAnalyze_InvalidConfig(t *testing.T) {
        require.Error(t, err)
        assert.Contains(t, err.Error(), "target file size must be positive")
 }
+
+// remotePlanCatalog plans every scan remotely and returns a plan-scoped IO, as
+// a REST catalog does when it vends storage credentials for a plan.
+type remotePlanCatalog struct {
+       analyzeCatalog
+       planIO *recordingPlanIO
+}
+
+func (c *remotePlanCatalog) SupportsRemoteScanPlanning() bool { return true }
+
+func (c *remotePlanCatalog) PlanFiles(context.Context, 
table.ScanPlanningRequest) (table.ScanPlanningResult, error) {
+       return table.ScanPlanningResult{IO: c.planIO}, nil
+}
+
+type recordingPlanIO struct {
+       closed int
+}
+
+func (p *recordingPlanIO) Load(context.Context) (iceio.IO, error) { return 
iceio.LocalFS{}, nil }
+
+func (p *recordingPlanIO) Close() error {
+       p.closed++
+
+       return nil
+}
+
+// A REST catalog keeps a plan with vended credentials alive until its IO is
+// closed, so Analyze must close the scan it plans with.
+func TestAnalyze_ClosesRemotePlan(t *testing.T) {
+       base := newAnalyzeTestTable(t)
+       planIO := &recordingPlanIO{}
+       cat := &remotePlanCatalog{analyzeCatalog: analyzeCatalog{metadata: 
base.Metadata()}, planIO: planIO}
+       tbl := table.New(base.Identifier(), base.Metadata(), 
base.MetadataLocation(),
+               func(context.Context) (iceio.IO, error) { return 
iceio.LocalFS{}, nil },
+               cat, 
table.WithScanPlanningDirective(string(table.ScanPlanningDirectiveServer), 
true))
+
+       _, err := compaction.Analyze(t.Context(), tbl, 
compaction.DefaultConfig())
+       require.NoError(t, err)
+
+       assert.Equal(t, 1, planIO.closed)
+}

Reply via email to