1515
1616use std:: collections:: HashMap ;
1717
18- use inverted_index:: { GcApplyInfo , GcScanDelta } ;
18+ use inverted_index:: { GcApplyInfo , GcScanDelta , IndexBlock , RSIndexResult } ;
1919
2020use super :: { NumericRangeTree , TrimEmptyLeavesResult , apply_signed_delta} ;
2121use crate :: NumericRangeNode ;
@@ -41,6 +41,7 @@ pub struct NodeGcDelta {
4141///
4242/// Returned by [`NumericRangeTree::apply_gc_to_node`].
4343#[ derive( Debug , Clone , Copy , Default ) ]
44+ #[ repr( C ) ]
4445pub struct SingleNodeGcResult {
4546 /// Information about the outcome of garbage collection on
4647 /// the inverted index stored within this node.
@@ -51,6 +52,7 @@ pub struct SingleNodeGcResult {
5152
5253/// Returned by [`NumericRangeTree::compact_if_sparse`].
5354#[ derive( Debug , Clone , Copy , Default ) ]
55+ #[ repr( C ) ]
5456pub struct CompactIfSparseResult {
5557 /// The change in the tree's inverted index memory usage, in bytes.
5658 /// Positive values indicate growth, negative values indicate shrinkage.
@@ -61,6 +63,51 @@ pub struct CompactIfSparseResult {
6163 pub node_size_delta : i64 ,
6264}
6365
66+ impl NumericRangeNode {
67+ /// Scan a single node's inverted index for GC work.
68+ ///
69+ /// Scan the inverted index associated with its range for deleted
70+ /// documents, and computes HLL registers for cardinality re-estimation.
71+ ///
72+ /// Returns `Some(NodeGcDelta)` if the node had GC work, `None` otherwise
73+ /// (either the node has no range, or no documents were deleted).
74+ pub fn scan_gc ( & self , doc_exists : & dyn Fn ( ffi:: t_docId ) -> bool ) -> Option < NodeGcDelta > {
75+ let range = self . range ( ) ?;
76+
77+ // HLL tracking for cardinality (re)estimation
78+ let mut majority_hll = Hll :: new ( ) ;
79+ let mut last_block_hll = Hll :: new ( ) ;
80+ let mut last_block_ptr: * const IndexBlock = std:: ptr:: null ( ) ;
81+
82+ let mut repair_fn = |res : & RSIndexResult < ' _ > , block : & IndexBlock | {
83+ let bp = block as * const IndexBlock ;
84+ if bp != last_block_ptr {
85+ majority_hll. merge ( & last_block_hll) ;
86+ last_block_hll. clear ( ) ;
87+ last_block_ptr = bp;
88+ }
89+ // SAFETY: We know this is a numeric index result
90+ let value = unsafe { res. as_numeric_unchecked ( ) } ;
91+ crate :: range:: update_cardinality ( & mut last_block_hll, value) ;
92+ } ;
93+
94+ let delta = range
95+ . entries ( )
96+ . scan_gc ( doc_exists, Some ( & mut repair_fn) )
97+ . ok ( )
98+ . flatten ( ) ?;
99+
100+ // Merge majority into last_block to get "with last block" registers.
101+ last_block_hll. merge ( & majority_hll) ;
102+
103+ Some ( NodeGcDelta {
104+ delta,
105+ registers_with_last_block : * last_block_hll. registers ( ) ,
106+ registers_without_last_block : * majority_hll. registers ( ) ,
107+ } )
108+ }
109+ }
110+
64111impl NumericRangeTree {
65112 /// Apply a GC delta to a single node by index.
66113 ///
0 commit comments