vortex_layout/plan/physical/plans/
take.rs1use std::borrow::Cow;
5use std::ops::Range;
6
7use vortex_array::Canonical;
8use vortex_array::EmptyMetadata;
9use vortex_array::IntoArray;
10use vortex_array::dtype::DType;
11use vortex_array::expr::BoundExpression;
12use vortex_array::expr::BoundLabels;
13use vortex_array::expr::ExactBoundExpr;
14use vortex_array::expr::label_bound_tree;
15use vortex_array::scalar_fn::fns::dynamic::DynamicComparison;
16use vortex_array::scalar_fn::is_negative_cost;
17use vortex_error::VortexResult;
18use vortex_mask::Mask;
19use vortex_session::registry::CachedId;
20
21use crate::plan::physical::Eval;
22use crate::plan::physical::EvalPlan;
23use crate::plan::physical::Plan;
24use crate::plan::physical::PlanChildren;
25use crate::plan::physical::PlanId;
26use crate::plan::physical::PlanParts;
27use crate::plan::physical::PlanRef;
28use crate::plan::physical::PlanVTable;
29use crate::plan::physical::Share;
30use crate::plan::physical::check_child_count;
31use crate::plan::physical::operators::OnceSource;
32use crate::plan::physical::operators::TakeSource;
33use crate::plan::physical::optimizer::PlanParentReduceRule;
34use crate::plan::physical::pipeline::Chain;
35use crate::plan::physical::pipeline::Compiler;
36use crate::plan::physical::pipeline::Reach;
37use crate::segments::SegmentId;
38
39const CODES: usize = 0;
40const VALUES: usize = 1;
41
42#[derive(Clone, Debug)]
44pub struct Take;
45
46pub type TakePlan = Plan<Take>;
48
49impl TakePlan {
50 pub(crate) unsafe fn from_children_unchecked(
57 dtype: DType,
58 row_count: u64,
59 children: PlanChildren,
60 ) -> Self {
61 PlanParts {
62 vtable: Take,
63 dtype,
64 row_count,
65 children,
66 data: (),
67 }
68 .into_typed()
69 }
70
71 pub fn new(codes: PlanRef, values: PlanRef) -> Self {
75 let dtype = values
76 .dtype()
77 .union_nullability(codes.dtype().nullability());
78 let row_count = codes.row_count();
79 PlanParts {
80 vtable: Take,
81 dtype,
82 row_count,
83 children: vec![codes, values].into(),
84 data: (),
85 }
86 .into_typed()
87 }
88
89 pub fn codes(&self) -> VortexResult<PlanRef> {
91 self.child_required(CODES)
92 }
93
94 pub fn values(&self) -> VortexResult<PlanRef> {
96 self.child_required(VALUES)
97 }
98}
99
100impl PlanVTable for Take {
101 type PlanData = ();
102 type Metadata = EmptyMetadata;
103
104 fn id(&self) -> PlanId {
105 static ID: CachedId = CachedId::new("vortex.plan.take");
106 *ID
107 }
108
109 fn metadata(_plan: &Plan<Self>) -> Option<Self::Metadata> {
110 Some(EmptyMetadata)
111 }
112
113 fn with_children(
114 plan: &Plan<Self>,
115 children: &PlanChildren,
116 _data: &mut Self::PlanData,
117 ) -> VortexResult<()> {
118 check_child_count("Take", children, 2)?;
119 let codes = children
120 .get(CODES)?
121 .ok_or_else(|| vortex_error::vortex_err!("Take codes child is absent"))?;
122 let values = children
123 .get(VALUES)?
124 .ok_or_else(|| vortex_error::vortex_err!("Take values child is absent"))?;
125 let dtype = values
126 .dtype()
127 .union_nullability(codes.dtype().nullability());
128 if codes.row_count() != plan.row_count() || &dtype != plan.dtype() {
129 vortex_error::vortex_bail!("Take child shape does not match the plan output");
130 }
131 Ok(())
132 }
133
134 fn child_name(_plan: &Plan<Self>, index: usize) -> Cow<'_, str> {
135 match index {
136 CODES => Cow::Borrowed("codes"),
137 VALUES => Cow::Borrowed("values"),
138 _ => Cow::Owned(format!("child[{index}]")),
139 }
140 }
141
142 fn compile(
145 plan: &Plan<Self>,
146 rows: Range<u64>,
147 mask: &Mask,
148 compiler: &mut Compiler<'_>,
149 ) -> VortexResult<Option<Chain>> {
150 let Some(codes) = compiler.compile(&plan.codes()?, rows, mask)? else {
151 return Ok(None);
152 };
153 let cache = compiler.values().clone();
154 let values_plan = plan.values()?;
155 if let Some(values) = cache.get(&values_plan) {
156 let source = TakeSource::new(plan.clone(), Some(values), None);
157 return Ok(Some(compiler.join(vec![codes], source)));
158 }
159 let values = values_plan;
160 let values_rows = 0..values.row_count();
161 let len = usize::try_from(values_rows.end)?;
162 let values = match compiler.compile(&values, values_rows, &Mask::new_true(len))? {
163 Some(values) => values,
164 None => Chain::new(OnceSource::new(
165 Canonical::empty(values.dtype()).into_array(),
166 )),
167 };
168 let source = TakeSource::new(plan.clone(), None, Some(cache));
169 Ok(Some(compiler.join_with(
170 vec![codes, values],
171 TakeSource::capacity,
172 source,
173 )))
174 }
175
176 fn reach(
177 plan: &Plan<Self>,
178 rows: Range<u64>,
179 at: &Reach,
180 visit: &mut dyn FnMut(SegmentId, Range<u64>),
181 ) -> VortexResult<()> {
182 let values = plan.values()?;
183 let len = values.row_count();
184 values.reach(0..len, &at.fixed(&rows), visit)?;
185 plan.codes()?.reach(rows, at, visit)
186 }
187}
188
189#[derive(Debug)]
202pub(crate) struct ExpressionTakeRule;
203
204type Labels = BoundLabels<(bool, bool, bool, bool)>;
207
208impl PlanParentReduceRule<Take> for ExpressionTakeRule {
209 type Parent = Eval;
210
211 fn reduce_parent(
212 &self,
213 child: &Plan<Take>,
214 parent: &Plan<Eval>,
215 _child_idx: usize,
216 ) -> VortexResult<Option<PlanRef>> {
217 let expression = parent.expression();
218 if expression.contains::<DynamicComparison>()? {
222 return Ok(None);
223 }
224 let codes_nullable = child.codes()?.dtype().is_nullable();
226 let labels = label_bound_tree(
227 expression,
228 |node| match node.as_scalar() {
229 Some(scalar_fn) => (
230 false,
231 scalar_fn.signature().is_strict() || !codes_nullable,
232 scalar_fn.signature().is_infallible(),
233 is_negative_cost(scalar_fn.id()),
234 ),
235 None => (true, true, true, true),
236 },
237 |acc, &child| {
238 (
239 acc.0 | child.0,
240 acc.1 & child.1,
241 acc.2 & child.2,
242 acc.3 & child.3,
243 )
244 },
245 );
246 let label = |node: &BoundExpression| {
247 labels
248 .get(&ExactBoundExpr(node.clone()))
249 .copied()
250 .unwrap_or((false, false, false, false))
251 };
252
253 let (references_root, is_strict, is_infallible, is_negative_cost) = label(expression);
254 if references_root && is_strict && is_infallible && !is_negative_cost {
255 if !expression.dtype().is_boolean() {
256 return Ok(None);
257 }
258 let values = child.values()?;
259 let Some(expression) = rebind_root(expression, child.dtype(), values.dtype()) else {
260 return Ok(None);
261 };
262 let values = EvalPlan::try_new(expression, values)?.into_plan();
263 let take = TakePlan::new(child.codes()?, values).into_plan();
264 if take.dtype() != parent.dtype() {
265 return Ok(None);
266 }
267 return Ok(Some(take));
268 }
269
270 let Some(inner) = negative_cost_part(expression, &labels) else {
271 return Ok(None);
272 };
273 let values = child.values()?;
274 let values = match values.as_opt::<Share>() {
275 Some(share) => share.child_plan()?,
276 None => values,
277 };
278 let Some(pushed) = rebind_root(&inner, child.dtype(), values.dtype()) else {
279 return Ok(None);
280 };
281 let values = EvalPlan::try_new(pushed, values)?.into_plan();
282 let take = TakePlan::new(child.codes()?, values).into_plan();
283 let outer = replace(
284 expression,
285 &inner,
286 &BoundExpression::new_root(take.dtype().clone()),
287 )?;
288 if outer.dtype() != expression.dtype() {
289 return Ok(None);
290 }
291 if outer.is_root() {
292 return Ok(Some(take));
293 }
294 Ok(Some(EvalPlan::try_new(outer, take)?.into_plan()))
295 }
296}
297
298fn negative_cost_part(expression: &BoundExpression, labels: &Labels) -> Option<BoundExpression> {
301 let (references_root, is_strict, is_infallible, is_negative_cost) =
302 labels.get(&ExactBoundExpr(expression.clone())).copied()?;
303 if !references_root {
304 return None;
305 }
306 if is_strict && is_infallible && is_negative_cost {
307 return (!expression.is_root()).then(|| expression.clone());
308 }
309 let mut reading = expression.children().iter().filter(|child| {
310 labels
311 .get(&ExactBoundExpr((*child).clone()))
312 .is_some_and(|label| label.0)
313 });
314 let child = reading.next()?;
315 if reading.next().is_some() {
316 return None;
317 }
318 negative_cost_part(child, labels)
319}
320
321fn rebind_root(expression: &BoundExpression, from: &DType, to: &DType) -> Option<BoundExpression> {
327 if from == to {
328 return Some(expression.clone());
329 }
330 replace(
331 expression,
332 &BoundExpression::new_root(from.clone()),
333 &BoundExpression::new_root(to.clone()),
334 )
335 .ok()
336}
337
338fn replace(
340 expression: &BoundExpression,
341 needle: &BoundExpression,
342 replacement: &BoundExpression,
343) -> VortexResult<BoundExpression> {
344 if ExactBoundExpr(expression.clone()) == ExactBoundExpr(needle.clone()) {
345 return Ok(replacement.clone());
346 }
347 if expression.children().is_empty() {
348 return Ok(expression.clone());
349 }
350 let children = expression
351 .children()
352 .iter()
353 .map(|child| replace(child, needle, replacement))
354 .collect::<VortexResult<Vec<_>>>()?;
355 expression.clone().with_children(children)
356}