| 152 | } |
| 153 | |
| 154 | Expected<RangeCursor> DataReader::rangeQuery(const QueryRange& range) const { |
| 155 | // Take the read-lock BEFORE the lookup (a concurrent createTopic rehash could |
| 156 | // otherwise move storages mid-lookup) and MOVE it into the cursor, which holds |
| 157 | // it for its lazy-iteration lifetime. |
| 158 | auto lock = engine_.lockEngine(); |
| 159 | const TopicStorage* storage = engine_.getTopicStorage(range.topic_id); |
| 160 | if (storage == nullptr) { |
| 161 | return PJ::unexpected(fmt::format("Topic {} not found", range.topic_id)); |
| 162 | } |
| 163 | // Raise the lower bound to the topic's retention floor so a straddling chunk's |
| 164 | // logically-evicted rows are never returned (RangeCursor skips rows < t_min, |
| 165 | // and yields nothing when t_min > t_max — i.e. a window entirely below floor). |
| 166 | const Timestamp t_min = std::max(range.t_min, storage->retentionFloor()); |
| 167 | return RangeCursor(storage->sealedChunks(), t_min, range.t_max, std::move(lock)); |
| 168 | } |
| 169 | |
| 170 | PJ::Expected<std::optional<MaterializedSample>> DataReader::latestAt(const QueryPoint& point) const { |
| 171 | auto lock = engine_.lockEngine(); |