Merge tile-versions for one `hilbert_prefix` according to the retention policy, operating at cell granularity. `versions` contains all [`TileEntry`] records for a single `hilbert_prefix` (the caller is responsible for grouping). They may arrive in any order; this function sorts internally. `horizon_ms` is the retention boundary expressed as an absolute `system_from_ms` timestamp: versions with `
(
versions: &[TileEntry],
reader: &SegmentReader<'_>,
schema: &ArraySchema,
horizon_ms: i64,
)
| 168 | /// `system_from_ms` timestamp: versions with `system_from_ms < horizon_ms` |
| 169 | /// are outside the retention window. |
| 170 | pub fn merge_for_retention( |
| 171 | versions: &[TileEntry], |
| 172 | reader: &SegmentReader<'_>, |
| 173 | schema: &ArraySchema, |
| 174 | horizon_ms: i64, |
| 175 | ) -> ArrayResult<RetentionMergeResult> { |
| 176 | if versions.is_empty() { |
| 177 | return Ok(RetentionMergeResult { |
| 178 | ceiling_tile: None, |
| 179 | keep_inhorizon: Vec::new(), |
| 180 | dropped_tile_ids: Vec::new(), |
| 181 | cells_carried_forward: 0, |
| 182 | }); |
| 183 | } |
| 184 | |
| 185 | // Partition into inside / outside by system_from_ms. |
| 186 | let mut inside: Vec<&TileEntry> = Vec::new(); |
| 187 | let mut outside: Vec<&TileEntry> = Vec::new(); |
| 188 | for entry in versions { |
| 189 | if entry.tile_id.system_from_ms >= horizon_ms { |
| 190 | inside.push(entry); |
| 191 | } else { |
| 192 | outside.push(entry); |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | // Collect TileIds for in-horizon pass-through. |
| 197 | let keep_inhorizon: Vec<TileId> = inside.iter().map(|e| e.tile_id).collect(); |
| 198 | |
| 199 | // If nothing is outside the horizon, there is nothing to merge. |
| 200 | if outside.is_empty() { |
| 201 | return Ok(RetentionMergeResult { |
| 202 | ceiling_tile: None, |
| 203 | keep_inhorizon, |
| 204 | dropped_tile_ids: Vec::new(), |
| 205 | cells_carried_forward: 0, |
| 206 | }); |
| 207 | } |
| 208 | |
| 209 | // Build the set of coord keys already covered by inside-horizon versions. |
| 210 | // Any coord present in any inside tile (regardless of RowKind) supersedes |
| 211 | // what the ceiling would contribute for that coord. |
| 212 | let mut inhorizon_coords: HashSet<Vec<u8>> = HashSet::new(); |
| 213 | for entry in &inside { |
| 214 | let tile_idx = find_tile_index(reader, entry.tile_id)?; |
| 215 | let payload = reader.read_tile(tile_idx)?; |
| 216 | if let TilePayload::Sparse(ref tile) = payload { |
| 217 | for trow in decode_sparse_rows(tile)? { |
| 218 | inhorizon_coords.insert(trow.coord_key); |
| 219 | } |
| 220 | } |
| 221 | // Dense tiles inside horizon contribute no coords to the exclusion set |
| 222 | // for the ceiling (dense tiles don't participate in sparse retention |
| 223 | // merge; their coords are not relevant here). |
| 224 | } |
| 225 | |
| 226 | // Walk outside-horizon versions newest → oldest, accumulating the ceiling. |
| 227 | // For each coord not yet seen, the first (newest) occurrence wins. |