From 850026050d353f1cc2e31707ad90abe66e093c48 Mon Sep 17 00:00:00 2001 From: yangjie01 Date: Sat, 26 Sep 2026 20:38:52 +0800 Subject: [PATCH 1/2] [core] Skip manual-only tag groups in preview time travel TagPreview.toOneAutoTag fed every snapshot's tag group into checkAndGetOneAutoTag, whose size==1 state check threw for groups holding only manually created tags, so a Hive preview query for a partition older than every resolvable snapshot crashed with the misleading 'more than 1 auto-created tags' error instead of the intended cannot-find failure. Return null for groups without auto-format tags and filter the nulls in the stream. Assisted-by: GLM-5.3 --- .../org/apache/paimon/tag/TagPreview.java | 6 +++++ .../org/apache/paimon/tag/TagPreviewTest.java | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/paimon-core/src/main/java/org/apache/paimon/tag/TagPreview.java b/paimon-core/src/main/java/org/apache/paimon/tag/TagPreview.java index 60edb03e084f..136a8a16b4f1 100644 --- a/paimon-core/src/main/java/org/apache/paimon/tag/TagPreview.java +++ b/paimon-core/src/main/java/org/apache/paimon/tag/TagPreview.java @@ -31,6 +31,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import static java.util.Collections.singletonMap; @@ -87,6 +88,7 @@ public Map timeTravel(DataTable table, String tag) { Optional findTag = tagManager.tags().values().stream() .map(this::toOneAutoTag) + .filter(Objects::nonNull) .filter(t -> t.compareTo(tag) <= 0) .max(Comparator.naturalOrder()); if (findTag.isPresent()) { @@ -103,6 +105,10 @@ private String toOneAutoTag(List tags) { autoTags.add(tag); } } + if (autoTags.isEmpty()) { + // a snapshot may carry only manually created tags: nothing to resolve here + return null; + } return TagAutoCreation.checkAndGetOneAutoTag(autoTags); } } diff --git a/paimon-core/src/test/java/org/apache/paimon/tag/TagPreviewTest.java b/paimon-core/src/test/java/org/apache/paimon/tag/TagPreviewTest.java index 36a4cf569f00..ef208384fa87 100644 --- a/paimon-core/src/test/java/org/apache/paimon/tag/TagPreviewTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/tag/TagPreviewTest.java @@ -38,6 +38,7 @@ import static org.apache.paimon.CoreOptions.SNAPSHOT_NUM_RETAINED_MAX; import static org.apache.paimon.CoreOptions.SNAPSHOT_NUM_RETAINED_MIN; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** Test for {@link TagPreview}. */ public class TagPreviewTest extends PrimaryKeyTableTestBase { @@ -91,6 +92,29 @@ public void testTimeTravel() { .containsAllEntriesOf(singletonMap(SCAN_TAG_NAME.key(), "2023-07-18")); } + @Test + public void testTimeTravelSkipsManualOnlySnapshots() throws Exception { + TagPreview preview = create(); + TableCommitImpl commit = table.newCommit(commitUser).ignoreEmptyCommit(false); + + // only manually named tags exist and no snapshot's preview time resolves the + // requested tag: the lookup must fail with the intended error instead of the + // misleading "more than 1 auto-created tags" + commit.commit(new ManifestCommittable(0, utcMills("2023-07-18T12:12:00"))); + table.createTag("my-manual-tag", 1); + + assertThatThrownBy(() -> preview.timeTravel(table, "2023-07-01")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Cannot find snapshot or tag"); + + // a date-shaped (auto-format) tag resolves normally through the short-circuit + table.createTag("2023-07-19", 1); + assertThat(preview.timeTravel(table, "2023-07-19")) + .containsAllEntriesOf(singletonMap(SCAN_TAG_NAME.key(), "2023-07-19")); + + commit.close(); + } + private TagPreview create() { Options options = new Options(); options.set(METASTORE_TAG_TO_PARTITION_PREVIEW, TagCreationMode.WATERMARK); From 84e4c79244b04b4f9267a1341a765fd00368e013 Mon Sep 17 00:00:00 2001 From: yangjie01 Date: Mon, 28 Sep 2026 03:35:23 +0800 Subject: [PATCH 2/2] test: cover preview time travel resolving an auto tag past a manual-only snapshot group --- .../org/apache/paimon/tag/TagPreviewTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/paimon-core/src/test/java/org/apache/paimon/tag/TagPreviewTest.java b/paimon-core/src/test/java/org/apache/paimon/tag/TagPreviewTest.java index ef208384fa87..0b52aecde174 100644 --- a/paimon-core/src/test/java/org/apache/paimon/tag/TagPreviewTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/tag/TagPreviewTest.java @@ -115,6 +115,35 @@ public void testTimeTravelSkipsManualOnlySnapshots() throws Exception { commit.close(); } + @Test + public void testTimeTravelResolvesAutoTagDespiteManualOnlyGroup() throws Exception { + TagPreview preview = create(); + Map dynamicOptions = new HashMap<>(); + dynamicOptions.put(SNAPSHOT_NUM_RETAINED_MIN.key(), "3"); + dynamicOptions.put(SNAPSHOT_NUM_RETAINED_MAX.key(), "3"); + TableCommitImpl commit = + table.copy(dynamicOptions).newCommit(commitUser).ignoreEmptyCommit(false); + + // snapshot 1 keeps an auto-format tag, snapshot 2 keeps only a manually named tag + commit.commit(new ManifestCommittable(0, utcMills("2023-07-18T12:12:00"))); + commit.commit(new ManifestCommittable(0, utcMills("2023-07-19T12:12:00"))); + table.createTag("2023-07-18", 1); + table.createTag("my-manual-tag", 2); + + // push the watermark past the request and expire snapshots 1 and 2, so the snapshot + // traversal finds nothing and the tags() fallback runs. The surviving auto tag on + // snapshot 1 must resolve even though snapshot 2's group holds only a manual tag: on + // master that manual-only group made toOneAutoTag throw and poisoned the whole max(). + for (int i = 0; i < 5; i++) { + commit.commit(new ManifestCommittable(0, utcMills("2023-07-21T12:12:00"))); + } + + assertThat(preview.timeTravel(table, "2023-07-19")) + .containsAllEntriesOf(singletonMap(SCAN_TAG_NAME.key(), "2023-07-18")); + + commit.close(); + } + private TagPreview create() { Options options = new Options(); options.set(METASTORE_TAG_TO_PARTITION_PREVIEW, TagCreationMode.WATERMARK);