Skip to content

Commit 36b0eaf

Browse files
committed
[core] End-to-end tests for snapshot-based sequence ordering
1 parent 471c78c commit 36b0eaf

1 file changed

Lines changed: 197 additions & 0 deletions

File tree

paimon-core/src/test/java/org/apache/paimon/table/PrimaryKeySimpleTableTest.java

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2672,4 +2672,201 @@ protected FileStoreTable createFileStoreTable(Consumer<Options> configure, RowTy
26722672
""));
26732673
return new PrimaryKeyFileStoreTable(FileIOFinder.find(tablePath), tablePath, tableSchema);
26742674
}
2675+
2676+
@Test
2677+
public void testSnapshotSequenceOrdering() throws Exception {
2678+
FileStoreTable table =
2679+
createFileStoreTable(
2680+
conf -> conf.set(CoreOptions.SEQUENCE_SNAPSHOT_ORDERING, true));
2681+
StreamTableWrite write = table.newWrite(commitUser);
2682+
StreamTableCommit commit = table.newCommit(commitUser);
2683+
2684+
// Snapshot 1: write pk=(1,10) many times so that the per-record sequence number is high.
2685+
for (int i = 0; i < 100; i++) {
2686+
write.write(rowData(1, 10, 999L));
2687+
}
2688+
commit.commit(0, write.prepareCommit(false, 0));
2689+
2690+
// Snapshot 2: write pk=(1,10) once with a lower value. Because the snapshot id (2)
2691+
// is larger than snapshot 1, this record should win even though its per-record sequence
2692+
// number is much lower.
2693+
write.write(rowData(1, 10, 1L));
2694+
commit.commit(1, write.prepareCommit(false, 1));
2695+
2696+
List<Split> splits = toSplits(table.newSnapshotReader().read().dataSplits());
2697+
TableRead read = table.newReadBuilder().newRead();
2698+
Function<InternalRow, String> toString =
2699+
r -> r.getInt(0) + "|" + r.getInt(1) + "|" + r.getLong(2);
2700+
List<String> result = getResult(read, splits, toString);
2701+
assertThat(result).containsExactly("1|10|1");
2702+
2703+
write.close();
2704+
commit.close();
2705+
}
2706+
2707+
@Test
2708+
public void testSnapshotSequenceOrderingFallsBackToSequenceWithinSnapshot() throws Exception {
2709+
FileStoreTable table =
2710+
createFileStoreTable(
2711+
conf -> conf.set(CoreOptions.SEQUENCE_SNAPSHOT_ORDERING, true));
2712+
StreamTableWrite write = table.newWrite(commitUser);
2713+
StreamTableCommit commit = table.newCommit(commitUser);
2714+
2715+
// Within a single snapshot, sequence number is the tiebreaker. The later write (999)
2716+
// gets a higher sequence number and should win.
2717+
write.write(rowData(1, 10, 1L));
2718+
write.write(rowData(1, 10, 999L));
2719+
commit.commit(0, write.prepareCommit(false, 0));
2720+
2721+
List<Split> splits = toSplits(table.newSnapshotReader().read().dataSplits());
2722+
TableRead read = table.newReadBuilder().newRead();
2723+
Function<InternalRow, String> toString =
2724+
r -> r.getInt(0) + "|" + r.getInt(1) + "|" + r.getLong(2);
2725+
List<String> result = getResult(read, splits, toString);
2726+
assertThat(result).containsExactly("1|10|999");
2727+
2728+
write.close();
2729+
commit.close();
2730+
}
2731+
2732+
@Test
2733+
public void testSnapshotSequenceOrderingCompactionPreservesInputSnapshotId() throws Exception {
2734+
FileStoreTable table =
2735+
createFileStoreTable(
2736+
conf -> conf.set(CoreOptions.SEQUENCE_SNAPSHOT_ORDERING, true));
2737+
StreamTableWrite write = table.newWrite(commitUser);
2738+
StreamTableCommit commit = table.newCommit(commitUser);
2739+
2740+
// Snapshot 1: write pk=(1,10) with val=100
2741+
write.write(rowData(1, 10, 100L));
2742+
commit.commit(0, write.prepareCommit(false, 0));
2743+
2744+
// Snapshot 2: write pk=(1,10) with val=200 (this should win after compaction)
2745+
write.write(rowData(1, 10, 200L));
2746+
commit.commit(1, write.prepareCommit(false, 1));
2747+
2748+
// Snapshot 3: write a DIFFERENT key pk=(1,20) — this creates a concurrent write
2749+
// that arrives between the compaction's base snapshot and its commit.
2750+
write.write(rowData(1, 20, 300L));
2751+
commit.commit(2, write.prepareCommit(false, 2));
2752+
2753+
// Snapshot 4: compact (processes files from snapshots 1+2+3)
2754+
write.compact(binaryRow(1), 0, true);
2755+
commit.commit(3, write.prepareCommit(false, 3));
2756+
2757+
// Snapshot 5: write pk=(1,10) with val=999 — this is newer than ALL previous writes
2758+
// and MUST win over the compacted result. If compaction incorrectly stamped with
2759+
// snapshot 4, and this write gets snapshot 5, it still wins (5 > 4). But if
2760+
// compaction stamped with max(inputs) = 3, this also wins (5 > 3). So we need a
2761+
// scenario where compaction shadow newer writes.
2762+
//
2763+
// The real test: verify the compacted file's minSequenceNumber is max(inputs), not
2764+
// the compaction commit's snapshotId. We check this via file metadata.
2765+
write.close();
2766+
commit.close();
2767+
2768+
List<DataSplit> splits = table.newSnapshotReader().read().dataSplits();
2769+
for (DataSplit split : splits) {
2770+
for (DataFileMeta file : split.dataFiles()) {
2771+
// After compaction, the compacted file should have minSequenceNumber equal
2772+
// to max(input snapshot ids), NOT the compaction commit's snapshot id (4).
2773+
// Input snapshots are 1, 2, 3 → max = 3. Compaction commit is snapshot 4.
2774+
assertThat(file.minSequenceNumber())
2775+
.as(
2776+
"Compacted file %s should have minSequenceNumber <= max(input snapshots), "
2777+
+ "not the compaction commit's snapshot id",
2778+
file.fileName())
2779+
.isLessThanOrEqualTo(3);
2780+
}
2781+
}
2782+
2783+
// Also verify the read result is correct
2784+
TableRead read = table.newReadBuilder().newRead();
2785+
Function<InternalRow, String> toString =
2786+
r -> r.getInt(0) + "|" + r.getInt(1) + "|" + r.getLong(2);
2787+
List<String> result = getResult(read, toSplits(splits), toString);
2788+
assertThat(result).containsExactlyInAnyOrder("1|10|200", "1|20|300");
2789+
}
2790+
2791+
@Test
2792+
public void testSnapshotSequenceOrderingWithChangelogInput() throws Exception {
2793+
FileStoreTable table =
2794+
createFileStoreTable(
2795+
conf -> {
2796+
conf.set(CoreOptions.SEQUENCE_SNAPSHOT_ORDERING, true);
2797+
conf.set(CHANGELOG_PRODUCER, ChangelogProducer.INPUT);
2798+
});
2799+
StreamTableWrite write = table.newWrite(commitUser);
2800+
StreamTableCommit commit = table.newCommit(commitUser);
2801+
2802+
write.write(rowData(1, 10, 100L));
2803+
commit.commit(0, write.prepareCommit(false, 0));
2804+
2805+
write.write(rowData(1, 10, 1L));
2806+
commit.commit(1, write.prepareCommit(false, 1));
2807+
2808+
List<Split> splits = toSplits(table.newSnapshotReader().read().dataSplits());
2809+
TableRead read = table.newReadBuilder().newRead();
2810+
Function<InternalRow, String> toString =
2811+
r -> r.getInt(0) + "|" + r.getInt(1) + "|" + r.getLong(2);
2812+
List<String> result = getResult(read, splits, toString);
2813+
assertThat(result).containsExactly("1|10|1");
2814+
2815+
write.close();
2816+
commit.close();
2817+
}
2818+
2819+
@Test
2820+
public void testSnapshotSequenceOrderingWithChangelogLookup() throws Exception {
2821+
FileStoreTable table =
2822+
createFileStoreTable(
2823+
conf -> {
2824+
conf.set(CoreOptions.SEQUENCE_SNAPSHOT_ORDERING, true);
2825+
conf.set(CHANGELOG_PRODUCER, LOOKUP);
2826+
});
2827+
StreamTableWrite write =
2828+
table.newWrite(commitUser).withIOManager(new IOManagerImpl(tempDir.toString()));
2829+
StreamTableCommit commit = table.newCommit(commitUser);
2830+
2831+
write.write(rowData(1, 10, 100L));
2832+
commit.commit(0, write.prepareCommit(false, 0));
2833+
2834+
write.write(rowData(1, 10, 1L));
2835+
commit.commit(1, write.prepareCommit(false, 1));
2836+
2837+
List<Split> splits = toSplits(table.newSnapshotReader().read().dataSplits());
2838+
TableRead read = table.newReadBuilder().newRead();
2839+
Function<InternalRow, String> toString =
2840+
r -> r.getInt(0) + "|" + r.getInt(1) + "|" + r.getLong(2);
2841+
List<String> result = getResult(read, splits, toString);
2842+
assertThat(result).containsExactly("1|10|1");
2843+
2844+
write.close();
2845+
commit.close();
2846+
}
2847+
2848+
@Test
2849+
public void testSnapshotSequenceOrderingDeleteFromLaterSnapshot() throws Exception {
2850+
FileStoreTable table =
2851+
createFileStoreTable(
2852+
conf -> conf.set(CoreOptions.SEQUENCE_SNAPSHOT_ORDERING, true));
2853+
StreamTableWrite write = table.newWrite(commitUser);
2854+
StreamTableCommit commit = table.newCommit(commitUser);
2855+
2856+
write.write(rowData(1, 10, 100L));
2857+
commit.commit(0, write.prepareCommit(false, 0));
2858+
2859+
write.write(rowDataWithKind(RowKind.DELETE, 1, 10, 100L));
2860+
commit.commit(1, write.prepareCommit(false, 1));
2861+
2862+
List<Split> splits = toSplits(table.newSnapshotReader().read().dataSplits());
2863+
TableRead read = table.newReadBuilder().newRead();
2864+
Function<InternalRow, String> toString =
2865+
r -> r.getInt(0) + "|" + r.getInt(1) + "|" + r.getLong(2);
2866+
List<String> result = getResult(read, splits, toString);
2867+
assertThat(result).isEmpty();
2868+
2869+
write.close();
2870+
commit.close();
2871+
}
26752872
}

0 commit comments

Comments
 (0)