@@ -22,6 +22,7 @@ use databend_common_base::runtime::execute_futures_in_parallel;
2222use databend_common_catalog:: plan:: BlockMetaWithHLL ;
2323use databend_common_catalog:: table:: Table ;
2424use databend_common_catalog:: table_context:: TableContext ;
25+ use databend_common_exception:: ErrorCode ;
2526use databend_common_exception:: Result ;
2627use databend_common_expression:: BlockMetaInfoPtr ;
2728use databend_common_expression:: BlockThresholds ;
@@ -387,10 +388,19 @@ impl TableMutationAggregator {
387388 . collect :: < Vec < _ > > ( ) ;
388389 Some ( SegmentStatistics :: new ( hlls, Vec :: new ( ) ) . to_bytes ( ) ?)
389390 } ;
390- let all_perfect = new_blocks. len ( ) > 1 ;
391+ // Only compaction/reclustering output may be force-marked perfect to keep
392+ // those operations at a fixed point. REPLACE/MERGE append after-images
393+ // must retain the physical perfect-block count from reduce_block_metas.
394+ let force_all_blocks_perfect = matches ! (
395+ self . write_segment_ctx. kind,
396+ MutationKind :: Compact | MutationKind :: Recluster
397+ ) && new_blocks. len ( ) > 1 ;
391398
392399 let ctx = self . write_segment_ctx . clone ( ) ;
393- tasks. push ( async move { ctx. write_segment ( new_blocks, new_hlls, all_perfect) . await } ) ;
400+ tasks. push ( async move {
401+ ctx. write_segment ( new_blocks, new_hlls, force_all_blocks_perfect)
402+ . await
403+ } ) ;
394404 }
395405
396406 let threads_nums = self . ctx . get_settings ( ) . get_max_threads ( ) ? as usize ;
@@ -507,7 +517,7 @@ impl TableMutationAggregator {
507517 let write_segment_ctx = self . write_segment_ctx . clone ( ) ;
508518
509519 tasks. push ( async move {
510- let mut all_perfect = false ;
520+ let mut force_all_blocks_perfect = false ;
511521 let ( new_blocks, new_hlls, origin_summary) = if let Some ( loc) = location {
512522 // read the old segment
513523 let compact_segment_info = SegmentsIO :: read_compact_segment (
@@ -559,11 +569,20 @@ impl TableMutationAggregator {
559569 let stats = generate_segment_stats ( new_hlls) ?;
560570 ( new_blocks, stats, Some ( segment_info. summary ) )
561571 } else {
562- // use by compact.
572+ // Only compact builds replacement segments without corresponding
573+ // entries in base_segments. Treating a missing base segment from
574+ // any other mutation as compact output could silently corrupt its
575+ // segment statistics.
576+ if !matches ! ( write_segment_ctx. kind, MutationKind :: Compact ) {
577+ return Err ( ErrorCode :: Internal ( format ! (
578+ "{} mutation references missing base segment index {}" ,
579+ write_segment_ctx. kind, index
580+ ) ) ) ;
581+ }
563582 assert ! ( segment_mutation. deleted_blocks. is_empty( ) ) ;
564583 // There are more than 1 blocks, means that the blocks can no longer be compacted.
565584 // They can be marked as perfect blocks.
566- all_perfect = segment_mutation. replaced_blocks . len ( ) > 1 ;
585+ force_all_blocks_perfect = segment_mutation. replaced_blocks . len ( ) > 1 ;
567586 let ( new_blocks, new_hlls) = segment_mutation
568587 . replaced_blocks
569588 . into_iter ( )
@@ -575,7 +594,7 @@ impl TableMutationAggregator {
575594 } ;
576595
577596 let new_segment_info = write_segment_ctx
578- . write_segment ( new_blocks, new_hlls, all_perfect )
597+ . write_segment ( new_blocks, new_hlls, force_all_blocks_perfect )
579598 . await ?;
580599
581600 Ok ( SegmentLite {
@@ -830,14 +849,14 @@ impl WriteSegmentCtx {
830849 & self ,
831850 blocks : Vec < Arc < BlockMeta > > ,
832851 stats : Option < Vec < u8 > > ,
833- all_perfect : bool ,
852+ force_all_blocks_perfect : bool ,
834853 ) -> Result < ( String , Statistics ) > {
835854 let location = self
836855 . location_gen
837856 . gen_segment_info_location ( self . table_meta_timestamps , false ) ;
838857 let mut new_summary =
839858 reduce_block_metas ( & blocks, self . thresholds , self . default_cluster_key ) ;
840- if all_perfect {
859+ if force_all_blocks_perfect {
841860 // To fix issue #13217.
842861 if new_summary. block_count > new_summary. perfect_block_count {
843862 warn ! (
0 commit comments