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)
+}