| 320 | } |
| 321 | |
| 322 | std::optional<SeriesSample> SeriesReader::sampleAt(std::size_t index) const { |
| 323 | std::size_t series_index = 0; |
| 324 | for (const TopicChunk& chunk : *chunks_) { |
| 325 | if (column_index_ >= chunk.columns.size() || chunk.stats.t_max < retention_floor_) { |
| 326 | continue; |
| 327 | } |
| 328 | for (std::size_t row = 0; row < chunk.stats.row_count; ++row) { |
| 329 | if (chunk.readTimestamp(row) < retention_floor_) { |
| 330 | continue; // logically evicted: not part of the virtual series |
| 331 | } |
| 332 | if (!readSeriesValue(chunk, column_index_, row).has_value()) { |
| 333 | continue; |
| 334 | } |
| 335 | if (series_index == index) { |
| 336 | return makeSeriesSample(chunk, column_index_, row); |
| 337 | } |
| 338 | ++series_index; |
| 339 | } |
| 340 | } |
| 341 | return std::nullopt; |
| 342 | } |
| 343 | |
| 344 | std::optional<std::size_t> SeriesReader::indexAtOrBeforeTime(Timestamp t) const { |
| 345 | std::optional<std::size_t> latest; |