vortex_layout/plan/physical/plans/
row_idx.rs1use std::fmt::Display;
5use std::fmt::Formatter;
6use std::ops::Range;
7
8use vortex_array::EmptyMetadata;
9use vortex_array::IntoArray;
10use vortex_array::dtype::DType;
11use vortex_array::dtype::FieldName;
12use vortex_array::dtype::Nullability;
13use vortex_array::dtype::PType;
14use vortex_array::dtype::StructFields;
15use vortex_array::expr::BoundExpression;
16use vortex_array::expr::transform::partition_bound;
17use vortex_array::expr::traversal::NodeExt;
18use vortex_array::expr::traversal::Transformed;
19use vortex_array::expr::traversal::TraversalOrder;
20use vortex_array::scalar_fn::fns::pack::Pack as PackFn;
21use vortex_buffer::Buffer;
22use vortex_error::VortexResult;
23use vortex_error::vortex_ensure_eq;
24use vortex_error::vortex_err;
25use vortex_mask::Mask;
26use vortex_session::registry::CachedId;
27
28use crate::layouts::row_idx::RowIdx as RowIdxFn;
29use crate::plan::physical::EvalPlan;
30use crate::plan::physical::PackPlan;
31use crate::plan::physical::Plan;
32use crate::plan::physical::PlanChildren;
33use crate::plan::physical::PlanId;
34use crate::plan::physical::PlanParts;
35use crate::plan::physical::PlanRef;
36use crate::plan::physical::PlanVTable;
37use crate::plan::physical::check_child_count;
38use crate::plan::physical::operators::OnceSource;
39use crate::plan::physical::pipeline::Chain;
40use crate::plan::physical::pipeline::Compiler;
41use crate::plan::physical::pipeline::Reach;
42use crate::plan::physical::plans::pack::rewrite_partition_root;
43use crate::segments::SegmentId;
44
45const ROW_IDX_PARTITION_NAME: &str = "row_idx";
46const CHILD_PARTITION_NAME: &str = "child";
47
48#[derive(Clone, Debug)]
50pub struct RowIdx;
51
52#[derive(Clone, Debug)]
54pub struct RowIdxData;
55
56pub type RowIdxPlan = Plan<RowIdx>;
58
59impl RowIdxPlan {
60 pub fn new(row_count: u64) -> Self {
62 PlanParts {
63 vtable: RowIdx,
64 dtype: row_idx_dtype(),
65 row_count,
66 children: PlanChildren::default(),
67 data: RowIdxData,
68 }
69 .into_typed()
70 }
71}
72
73pub fn row_idx_dtype() -> DType {
75 DType::Primitive(PType::U64, Nullability::NonNullable)
76}
77
78impl PlanVTable for RowIdx {
79 type PlanData = RowIdxData;
80 type Metadata = EmptyMetadata;
81
82 fn id(&self) -> PlanId {
83 static ID: CachedId = CachedId::new("vortex.plan.row_idx");
84 *ID
85 }
86
87 fn metadata(_plan: &Plan<Self>) -> Option<Self::Metadata> {
88 Some(EmptyMetadata)
89 }
90
91 fn with_children(
92 _plan: &Plan<Self>,
93 children: &PlanChildren,
94 _data: &mut Self::PlanData,
95 ) -> VortexResult<()> {
96 check_child_count("RowIdx", children, 0)
97 }
98
99 fn compile(
100 plan: &Plan<Self>,
101 rows: Range<u64>,
102 mask: &Mask,
103 compiler: &mut Compiler<'_>,
104 ) -> VortexResult<Option<Chain>> {
105 let _ = plan;
106 if mask.all_false() {
107 return Ok(None);
108 }
109 let offset = compiler.row_offset();
110 let indices = Buffer::from_iter(rows.start + offset..rows.end + offset).into_array();
111 let indices = if mask.all_true() {
112 indices
113 } else {
114 indices.filter(mask.clone())?
115 };
116 Ok(Some(Chain::new(OnceSource::new(indices))))
117 }
118
119 fn reach(
120 plan: &Plan<Self>,
121 rows: Range<u64>,
122 at: &Reach,
123 visit: &mut dyn FnMut(SegmentId, Range<u64>),
124 ) -> VortexResult<()> {
125 let _ = (plan, rows, at, visit);
126 Ok(())
127 }
128}
129
130pub fn plan_row_idx_expression(
136 expression: BoundExpression,
137 child: PlanRef,
138) -> VortexResult<PlanRef> {
139 let partitioned = partition_bound(expression.clone(), |node| {
140 if node
141 .as_scalar()
142 .is_some_and(|scalar_fn| scalar_fn.is::<RowIdxFn>())
143 {
144 vec![RowIdxExpressionPartition::RowIdx]
145 } else if node.is_root() {
146 vec![RowIdxExpressionPartition::Child]
147 } else {
148 vec![]
149 }
150 })?;
151
152 if partitioned.partition_annotations.is_empty() {
153 let row_domain = PackPlan::try_new(
154 StructFields::empty(),
155 Nullability::NonNullable,
156 child.row_count(),
157 Vec::new(),
158 None,
159 )?
160 .into_plan();
161 return Ok(EvalPlan::try_new(expression, row_domain)?.into_plan());
162 }
163
164 if partitioned.partition_annotations.len() == 1 {
165 return match partitioned.partition_annotations[0] {
166 RowIdxExpressionPartition::RowIdx => {
167 let expression = replace_row_idx(expression)?;
168 let values = RowIdxPlan::new(child.row_count()).into_plan();
169 Ok(EvalPlan::try_new(expression, values)?.into_plan())
170 }
171 RowIdxExpressionPartition::Child => {
172 Ok(EvalPlan::try_new(expression, child)?.into_plan())
173 }
174 };
175 }
176
177 vortex_ensure_eq!(
178 partitioned.partition_annotations.len(),
179 2,
180 "Row-index expression produced more than two partitions"
181 );
182 let row_idx_index = partitioned
183 .partition_annotations
184 .iter()
185 .position(|partition| *partition == RowIdxExpressionPartition::RowIdx)
186 .ok_or_else(|| vortex_err!("Row-index expression has no row-index partition"))?;
187 let child_index = partitioned
188 .partition_annotations
189 .iter()
190 .position(|partition| *partition == RowIdxExpressionPartition::Child)
191 .ok_or_else(|| vortex_err!("Row-index expression has no data partition"))?;
192
193 let row_idx_partition = &partitioned.partitions[row_idx_index];
194 let child_partition = &partitioned.partitions[child_index];
195 let (Some(row_idx_pack), Some(child_pack)) = (
196 row_idx_partition
197 .as_scalar()
198 .and_then(|scalar_fn| scalar_fn.as_opt::<PackFn>()),
199 child_partition
200 .as_scalar()
201 .and_then(|scalar_fn| scalar_fn.as_opt::<PackFn>()),
202 ) else {
203 return Err(vortex_err!(
204 "Row-index expression partitions must be struct packs"
205 ));
206 };
207 let row_idx_partition_name = partitioned.partition_names[row_idx_index].clone();
208 let child_partition_name = partitioned.partition_names[child_index].clone();
209 let mut collapsed = Vec::with_capacity(2);
210
211 let row_idx_expression = if row_idx_partition.children().len() == 1 {
212 let Some(value_name) = row_idx_pack.names.get(0) else {
213 return Err(vortex_err!("Row-index expression partition is empty"));
214 };
215 collapsed.push((row_idx_partition_name, value_name.clone()));
216 row_idx_partition.children()[0].clone()
217 } else {
218 row_idx_partition.clone()
219 };
220 let child_expression = if child_partition.children().len() == 1 {
221 let Some(value_name) = child_pack.names.get(0) else {
222 return Err(vortex_err!("Data expression partition is empty"));
223 };
224 collapsed.push((child_partition_name, value_name.clone()));
225 child_partition.children()[0].clone()
226 } else {
227 child_partition.clone()
228 };
229
230 let row_count = child.row_count();
231 let row_idx_expression = replace_row_idx(row_idx_expression)?;
232 let row_idx_plan =
233 EvalPlan::try_new(row_idx_expression, RowIdxPlan::new(row_count).into_plan())?.into_plan();
234 let child_plan = EvalPlan::try_new(child_expression, child)?.into_plan();
235 let fields = StructFields::from_iter([
236 (ROW_IDX_PARTITION_NAME, row_idx_plan.dtype().clone()),
237 (CHILD_PARTITION_NAME, child_plan.dtype().clone()),
238 ]);
239 let partitions = PackPlan::try_new(
240 fields,
241 Nullability::NonNullable,
242 row_count,
243 vec![row_idx_plan, child_plan],
244 None,
245 )?;
246 let residual =
247 rewrite_partition_root(partitioned.root, partitions.dtype().clone(), &collapsed)?;
248
249 Ok(EvalPlan::try_new(residual, partitions.into_plan())?.into_plan())
250}
251
252fn replace_row_idx(expression: BoundExpression) -> VortexResult<BoundExpression> {
253 Ok(expression
254 .transform_down(|node| {
255 if node
256 .as_scalar()
257 .is_some_and(|scalar_fn| scalar_fn.is::<RowIdxFn>())
258 {
259 Ok(Transformed {
260 value: BoundExpression::new_root(row_idx_dtype()),
261 changed: true,
262 order: TraversalOrder::Skip,
263 })
264 } else {
265 Ok(Transformed::no(node))
266 }
267 })?
268 .into_inner())
269}
270
271#[derive(Clone, Copy, PartialEq, Eq, Hash)]
272enum RowIdxExpressionPartition {
273 RowIdx,
274 Child,
275}
276
277impl RowIdxExpressionPartition {
278 fn name(self) -> &'static str {
279 match self {
280 Self::RowIdx => ROW_IDX_PARTITION_NAME,
281 Self::Child => CHILD_PARTITION_NAME,
282 }
283 }
284}
285
286impl Display for RowIdxExpressionPartition {
287 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
288 formatter.write_str(self.name())
289 }
290}
291
292impl From<RowIdxExpressionPartition> for FieldName {
293 fn from(partition: RowIdxExpressionPartition) -> Self {
294 FieldName::from(partition.name())
295 }
296}