| 369 | } |
| 370 | |
| 371 | std::optional<std::size_t> SeriesReader::indexAtOrAfterTime(Timestamp t) const { |
| 372 | std::size_t series_index = 0; |
| 373 | for (const TopicChunk& chunk : *chunks_) { |
| 374 | if (chunk.stats.row_count == 0 || column_index_ >= chunk.columns.size() || chunk.stats.t_max < retention_floor_) { |
| 375 | continue; |
| 376 | } |
| 377 | if (chunk.stats.t_max < t) { |
| 378 | for (std::size_t row = 0; row < chunk.stats.row_count; ++row) { |
| 379 | if (chunk.readTimestamp(row) < retention_floor_) { |
| 380 | continue; // logically evicted: does not advance the virtual index |
| 381 | } |
| 382 | if (readSeriesValue(chunk, column_index_, row).has_value()) { |
| 383 | ++series_index; |
| 384 | } |
| 385 | } |
| 386 | continue; |
| 387 | } |
| 388 | for (std::size_t row = 0; row < chunk.stats.row_count; ++row) { |
| 389 | const Timestamp ts = chunk.readTimestamp(row); |
| 390 | if (ts < retention_floor_) { |
| 391 | continue; // logically evicted |
| 392 | } |
| 393 | if (!readSeriesValue(chunk, column_index_, row).has_value()) { |
| 394 | continue; |
| 395 | } |
| 396 | if (ts >= t) { |
| 397 | return series_index; |
| 398 | } |
| 399 | ++series_index; |
| 400 | } |
| 401 | } |
| 402 | return std::nullopt; |
| 403 | } |
| 404 | |
| 405 | std::optional<SeriesSample> SeriesReader::sampleAtOrBeforeTime(Timestamp t) const { |
| 406 | const auto index = indexAtOrBeforeTime(t); |