use std::borrow::Cow;
use std::ops::Range;
use vortex_array::Canonical;
use vortex_array::EmptyMetadata;
use vortex_array::IntoArray;
use vortex_array::dtype::DType;
use vortex_array::expr::BoundExpression;
use vortex_array::expr::BoundLabels;
use vortex_array::expr::ExactBoundExpr;
use vortex_array::expr::label_bound_tree;
use vortex_array::scalar_fn::fns::dynamic::DynamicComparison;
use vortex_array::scalar_fn::is_negative_cost;
use vortex_error::VortexResult;
use vortex_mask::Mask;
use vortex_session::registry::CachedId;
use crate::plan::physical::Eval;
use crate::plan::physical::EvalPlan;
use crate::plan::physical::Plan;
use crate::plan::physical::PlanChildren;
use crate::plan::physical::PlanId;
use crate::plan::physical::PlanParts;
use crate::plan::physical::PlanRef;
use crate::plan::physical::PlanVTable;
use crate::plan::physical::Share;
use crate::plan::physical::check_child_count;
use crate::plan::physical::operators::OnceSource;
use crate::plan::physical::operators::TakeSource;
use crate::plan::physical::optimizer::PlanParentReduceRule;
use crate::plan::physical::pipeline::Chain;
use crate::plan::physical::pipeline::Compiler;
use crate::plan::physical::pipeline::Reach;
use crate::segments::SegmentId;
const CODES: usize = 0;
const VALUES: usize = 1;
#[derive(Clone, Debug)]
pub struct Take;
pub type TakePlan = Plan<Take>;
impl TakePlan {
pub(crate) unsafe fn from_children_unchecked(
dtype: DType,
row_count: u64,
children: PlanChildren,
) -> Self {
PlanParts {
vtable: Take,
dtype,
row_count,
children,
data: (),
}
.into_typed()
}
pub fn new(codes: PlanRef, values: PlanRef) -> Self {
let dtype = values
.dtype()
.union_nullability(codes.dtype().nullability());
let row_count = codes.row_count();
PlanParts {
vtable: Take,
dtype,
row_count,
children: vec![codes, values].into(),
data: (),
}
.into_typed()
}
pub fn codes(&self) -> VortexResult<PlanRef> {
self.child_required(CODES)
}
pub fn values(&self) -> VortexResult<PlanRef> {
self.child_required(VALUES)
}
}
impl PlanVTable for Take {
type PlanData = ();
type Metadata = EmptyMetadata;
fn id(&self) -> PlanId {
static ID: CachedId = CachedId::new("vortex.plan.take");
*ID
}
fn metadata(_plan: &Plan<Self>) -> Option<Self::Metadata> {
Some(EmptyMetadata)
}
fn with_children(
plan: &Plan<Self>,
children: &PlanChildren,
_data: &mut Self::PlanData,
) -> VortexResult<()> {
check_child_count("Take", children, 2)?;
let codes = children
.get(CODES)?
.ok_or_else(|| vortex_error::vortex_err!("Take codes child is absent"))?;
let values = children
.get(VALUES)?
.ok_or_else(|| vortex_error::vortex_err!("Take values child is absent"))?;
let dtype = values
.dtype()
.union_nullability(codes.dtype().nullability());
if codes.row_count() != plan.row_count() || &dtype != plan.dtype() {
vortex_error::vortex_bail!("Take child shape does not match the plan output");
}
Ok(())
}
fn child_name(_plan: &Plan<Self>, index: usize) -> Cow<'_, str> {
match index {
CODES => Cow::Borrowed("codes"),
VALUES => Cow::Borrowed("values"),
_ => Cow::Owned(format!("child[{index}]")),
}
}
fn compile(
plan: &Plan<Self>,
rows: Range<u64>,
mask: &Mask,
compiler: &mut Compiler<'_>,
) -> VortexResult<Option<Chain>> {
let Some(codes) = compiler.compile(&plan.codes()?, rows, mask)? else {
return Ok(None);
};
let cache = compiler.values().clone();
let values_plan = plan.values()?;
if let Some(values) = cache.get(&values_plan) {
let source = TakeSource::new(plan.clone(), Some(values), None);
return Ok(Some(compiler.join(vec![codes], source)));
}
let values = values_plan;
let values_rows = 0..values.row_count();
let len = usize::try_from(values_rows.end)?;
let values = match compiler.compile(&values, values_rows, &Mask::new_true(len))? {
Some(values) => values,
None => Chain::new(OnceSource::new(
Canonical::empty(values.dtype()).into_array(),
)),
};
let source = TakeSource::new(plan.clone(), None, Some(cache));
Ok(Some(compiler.join_with(
vec![codes, values],
TakeSource::capacity,
source,
)))
}
fn reach(
plan: &Plan<Self>,
rows: Range<u64>,
at: &Reach,
visit: &mut dyn FnMut(SegmentId, Range<u64>),
) -> VortexResult<()> {
let values = plan.values()?;
let len = values.row_count();
values.reach(0..len, &at.fixed(&rows), visit)?;
plan.codes()?.reach(rows, at, visit)
}
}
#[derive(Debug)]
pub(crate) struct ExpressionTakeRule;
type Labels = BoundLabels<(bool, bool, bool, bool)>;
impl PlanParentReduceRule<Take> for ExpressionTakeRule {
type Parent = Eval;
fn reduce_parent(
&self,
child: &Plan<Take>,
parent: &Plan<Eval>,
_child_idx: usize,
) -> VortexResult<Option<PlanRef>> {
let expression = parent.expression();
if expression.contains::<DynamicComparison>()? {
return Ok(None);
}
let codes_nullable = child.codes()?.dtype().is_nullable();
let labels = label_bound_tree(
expression,
|node| match node.as_scalar() {
Some(scalar_fn) => (
false,
scalar_fn.signature().is_strict() || !codes_nullable,
scalar_fn.signature().is_infallible(),
is_negative_cost(scalar_fn.id()),
),
None => (true, true, true, true),
},
|acc, &child| {
(
acc.0 | child.0,
acc.1 & child.1,
acc.2 & child.2,
acc.3 & child.3,
)
},
);
let label = |node: &BoundExpression| {
labels
.get(&ExactBoundExpr(node.clone()))
.copied()
.unwrap_or((false, false, false, false))
};
let (references_root, is_strict, is_infallible, is_negative_cost) = label(expression);
if references_root && is_strict && is_infallible && !is_negative_cost {
if !expression.dtype().is_boolean() {
return Ok(None);
}
let values = child.values()?;
let Some(expression) = rebind_root(expression, child.dtype(), values.dtype()) else {
return Ok(None);
};
let values = EvalPlan::try_new(expression, values)?.into_plan();
let take = TakePlan::new(child.codes()?, values).into_plan();
if take.dtype() != parent.dtype() {
return Ok(None);
}
return Ok(Some(take));
}
let Some(inner) = negative_cost_part(expression, &labels) else {
return Ok(None);
};
let values = child.values()?;
let values = match values.as_opt::<Share>() {
Some(share) => share.child_plan()?,
None => values,
};
let Some(pushed) = rebind_root(&inner, child.dtype(), values.dtype()) else {
return Ok(None);
};
let values = EvalPlan::try_new(pushed, values)?.into_plan();
let take = TakePlan::new(child.codes()?, values).into_plan();
let outer = replace(
expression,
&inner,
&BoundExpression::new_root(take.dtype().clone()),
)?;
if outer.dtype() != expression.dtype() {
return Ok(None);
}
if outer.is_root() {
return Ok(Some(take));
}
Ok(Some(EvalPlan::try_new(outer, take)?.into_plan()))
}
}
fn negative_cost_part(expression: &BoundExpression, labels: &Labels) -> Option<BoundExpression> {
let (references_root, is_strict, is_infallible, is_negative_cost) =
labels.get(&ExactBoundExpr(expression.clone())).copied()?;
if !references_root {
return None;
}
if is_strict && is_infallible && is_negative_cost {
return (!expression.is_root()).then(|| expression.clone());
}
let mut reading = expression.children().iter().filter(|child| {
labels
.get(&ExactBoundExpr((*child).clone()))
.is_some_and(|label| label.0)
});
let child = reading.next()?;
if reading.next().is_some() {
return None;
}
negative_cost_part(child, labels)
}
fn rebind_root(expression: &BoundExpression, from: &DType, to: &DType) -> Option<BoundExpression> {
if from == to {
return Some(expression.clone());
}
replace(
expression,
&BoundExpression::new_root(from.clone()),
&BoundExpression::new_root(to.clone()),
)
.ok()
}
fn replace(
expression: &BoundExpression,
needle: &BoundExpression,
replacement: &BoundExpression,
) -> VortexResult<BoundExpression> {
if ExactBoundExpr(expression.clone()) == ExactBoundExpr(needle.clone()) {
return Ok(replacement.clone());
}
if expression.children().is_empty() {
return Ok(expression.clone());
}
let children = expression
.children()
.iter()
.map(|child| replace(child, needle, replacement))
.collect::<VortexResult<Vec<_>>>()?;
expression.clone().with_children(children)
}