@@ -1330,6 +1330,7 @@ TEST_F(LakePersistentIndexTest, test_ingest_sst_preserves_shared_flag_for_new_ss
13301330 if (sst.filename () == sst_filename) {
13311331 match_count++;
13321332 EXPECT_TRUE (sst.shared ()) << " sst_meta.shared=true must be preserved in committed sstable_meta" ;
1333+ EXPECT_EQ (1 , sst.generation_version ()) << " ingest_sst stamps the generation version (the ingest version)" ;
13331334 }
13341335 }
13351336 EXPECT_EQ (match_count, 1 );
@@ -1573,4 +1574,101 @@ TEST_F(LakePersistentIndexTest, test_load_dels_parallel_matches_single_pass) {
15731574 }
15741575}
15751576
1577+ TEST (AssignGenerationVersionsTest, stamps_new_preserves_existing_and_legacy) {
1578+ PersistentIndexSstableMetaPB prev;
1579+ {
1580+ auto * s = prev.add_sstables ();
1581+ s->set_filename (" existing_v3.sst" );
1582+ s->set_generation_version (3 );
1583+ }
1584+ {
1585+ auto * s = prev.add_sstables ();
1586+ s->set_filename (" legacy_zero.sst" ); // version unset == 0
1587+ }
1588+
1589+ PersistentIndexSstableMetaPB cur;
1590+ {
1591+ auto * s = cur.add_sstables ();
1592+ s->set_filename (" existing_v3.sst" ); // reappears, version dropped by copy
1593+ }
1594+ {
1595+ auto * s = cur.add_sstables ();
1596+ s->set_filename (" legacy_zero.sst" ); // legacy, still unset
1597+ }
1598+ {
1599+ auto * s = cur.add_sstables ();
1600+ s->set_filename (" fresh_new.sst" ); // brand new this publish
1601+ }
1602+ {
1603+ auto * s = cur.add_sstables ();
1604+ s->set_filename (" ingested.sst" );
1605+ s->set_generation_version (9 ); // producer already stamped, absent from prev
1606+ }
1607+ {
1608+ auto * s = cur.add_sstables ();
1609+ s->set_filename (" existing_v3.sst" );
1610+ s->set_generation_version (7 ); // same filename as prev@3 but already non-zero
1611+ }
1612+
1613+ LakePersistentIndex::assign_generation_versions (&cur, prev, /* generation_version=*/ 100 );
1614+
1615+ ASSERT_EQ (5 , cur.sstables_size ());
1616+ EXPECT_EQ (3 , cur.sstables (0 ).generation_version ()); // existing, local 0 -> carried from prev (3)
1617+ EXPECT_EQ (0 , cur.sstables (1 ).generation_version ()); // legacy 0 -> STAYS 0 (present in prev); vacuum retains
1618+ EXPECT_EQ (100 , cur.sstables (2 ).generation_version ()); // new (absent from prev) -> new version
1619+ EXPECT_EQ (9 , cur.sstables (3 ).generation_version ()); // producer-stamped non-zero -> preserved (absent from prev)
1620+ EXPECT_EQ (7 , cur.sstables (4 ).generation_version ()); // non-zero -> NEVER overwritten, even though prev has @3
1621+ }
1622+
1623+ TEST_F (LakePersistentIndexTest, test_commit_stamps_version) {
1624+ const int N = 100 ;
1625+ const int64_t publish_version = 7 ;
1626+ auto tablet_id = _tablet_metadata->id ();
1627+ auto index = std::make_unique<LakePersistentIndex>(_tablet_mgr.get (), tablet_id);
1628+ ASSERT_OK (index->init (_tablet_metadata));
1629+
1630+ using Key = uint64_t ;
1631+ std::vector<Key> keys (N);
1632+ std::vector<Slice> key_slices;
1633+ std::vector<IndexValue> values;
1634+ key_slices.reserve (N);
1635+ values.reserve (N);
1636+ for (int j = 0 ; j < N; j++) {
1637+ keys[j] = j;
1638+ key_slices.emplace_back ((uint8_t *)(&keys[j]), sizeof (Key));
1639+ values.emplace_back (j * 2 );
1640+ }
1641+ index->prepare (EditVersion (publish_version, 0 ), 0 );
1642+ std::vector<IndexValue> old_values (N);
1643+ ASSERT_OK (index->upsert (N, key_slices.data (), values.data (), old_values.data ()));
1644+ ASSERT_OK (index->flush_memtable (true ));
1645+ ASSERT_OK (index->sync_flush_all_memtables (10000000 ));
1646+
1647+ Tablet tablet (_tablet_mgr.get (), tablet_id);
1648+ auto meta = std::make_shared<TabletMetadata>();
1649+ meta->CopyFrom (*_tablet_metadata);
1650+ meta->set_version (publish_version);
1651+ MetaFileBuilder builder (tablet, meta);
1652+ ASSERT_OK (index->commit (&builder));
1653+
1654+ // A freshly-flushed sstable is stamped with the publish version.
1655+ ASSERT_GE (meta->sstable_meta ().sstables_size (), 1 );
1656+ for (const auto & s : meta->sstable_meta ().sstables ()) {
1657+ EXPECT_EQ (publish_version, s.generation_version ()) << " freshly-flushed sstable must carry its publish version" ;
1658+ }
1659+
1660+ // Re-commit at a later version WITHOUT new data: existing sstables must keep
1661+ // their original version (no re-stamp), because they are present in
1662+ // the previous sstable_meta the builder carries forward.
1663+ auto meta2 = std::make_shared<TabletMetadata>();
1664+ meta2->CopyFrom (*meta); // carries the publish_version sstable_meta forward
1665+ meta2->set_version (publish_version + 10 );
1666+ MetaFileBuilder builder2 (tablet, meta2);
1667+ ASSERT_OK (index->commit (&builder2));
1668+ ASSERT_GE (meta2->sstable_meta ().sstables_size (), 1 );
1669+ for (const auto & s : meta2->sstable_meta ().sstables ()) {
1670+ EXPECT_EQ (publish_version, s.generation_version ()) << " re-commit must not re-stamp an existing sstable" ;
1671+ }
1672+ }
1673+
15761674} // namespace starrocks::lake
0 commit comments