vortex_layout/scan/v2/cache.rs
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! State derived from a [`ScanFile`](super::ScanFile) that repeated V2 scans can share.
5
6use std::sync::Arc;
7
8use parking_lot::Mutex;
9use rustc_hash::FxHashMap;
10use vortex_array::expr::BoundExpression;
11use vortex_array::expr::ExactBoundExpr;
12
13use super::io::SegmentRanges;
14use crate::plan::physical::PlanRef;
15use crate::plan::physical::pipeline::ValueCache;
16
17/// Reusable state for repeated V2 scans of one file, the V2 counterpart of a cached
18/// [`LayoutReader`](crate::LayoutReader) tree.
19///
20/// Every scan otherwise lowers the layout into a fresh physical plan, optimizes its projection
21/// over it, indexes every segment's byte range, and decodes the dictionary values it needs. A scan
22/// that finds an entry here reuses it, and one that misses builds the entry and keeps it for later
23/// scans. The lowered plan keeps the children it has initialized, each distinct projection keeps
24/// its optimized plan, and dictionary values stay keyed by the cached plan that produced them.
25///
26/// A cache describes one file's layout, segment locations, and segment source. Use a new cache
27/// when any of them changes. Cached plans and values live as long as the cache, one set per
28/// distinct projection.
29#[derive(Clone, Default)]
30pub struct ScanCache(Arc<Inner>);
31
32#[derive(Default)]
33struct Inner {
34 root: Mutex<Option<PlanRef>>,
35 /// Optimized plans of non-identity projections. Values are cached by the plan that produced
36 /// them, so a projection must reuse its plan for its values to be found again.
37 projections: Mutex<FxHashMap<ExactBoundExpr, PlanRef>>,
38 ranges: Mutex<Option<SegmentRanges>>,
39 values: ValueCache,
40}
41
42impl ScanCache {
43 /// The lowered root plan an earlier scan kept.
44 pub(super) fn root(&self) -> Option<PlanRef> {
45 self.0.root.lock().clone()
46 }
47
48 /// Keeps `root`, returning the plan already kept if another scan got there first.
49 pub(super) fn insert_root(&self, root: PlanRef) -> PlanRef {
50 self.0.root.lock().get_or_insert(root).clone()
51 }
52
53 /// The optimized plan of `projection` an earlier scan kept.
54 pub(super) fn projection(&self, projection: &BoundExpression) -> Option<PlanRef> {
55 self.0
56 .projections
57 .lock()
58 .get(&ExactBoundExpr(projection.clone()))
59 .cloned()
60 }
61
62 /// Keeps the optimized plan of `projection`, returning the plan already kept if another scan
63 /// got there first.
64 pub(super) fn insert_projection(&self, projection: BoundExpression, plan: PlanRef) -> PlanRef {
65 self.0
66 .projections
67 .lock()
68 .entry(ExactBoundExpr(projection))
69 .or_insert(plan)
70 .clone()
71 }
72
73 /// The segment byte-range index an earlier scan kept.
74 pub(super) fn ranges(&self) -> Option<SegmentRanges> {
75 self.0.ranges.lock().clone()
76 }
77
78 /// Keeps `ranges`, returning the index already kept if another scan got there first.
79 pub(super) fn insert_ranges(&self, ranges: SegmentRanges) -> SegmentRanges {
80 Arc::clone(self.0.ranges.lock().get_or_insert(ranges))
81 }
82
83 pub(super) fn values(&self) -> ValueCache {
84 self.0.values.clone()
85 }
86}