use std::borrow::Cow;
use vortex_array::EmptyMetadata;
use vortex_array::dtype::DType;
use vortex_array::expr::ExactBoundExpr;
use vortex_array::expr::label_bound_tree;
use vortex_error::VortexResult;
use vortex_session::registry::CachedId;
use crate::plan::Eval;
use crate::plan::EvalPlan;
use crate::plan::Plan;
use crate::plan::PlanChildren;
use crate::plan::PlanId;
use crate::plan::PlanParts;
use crate::plan::PlanRef;
use crate::plan::PlanVTable;
use crate::plan::check_child_count;
use crate::plan::optimizer::PlanParentReduceRule;
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();
unsafe { Self::from_children_unchecked(dtype, row_count, vec![codes, values].into()) }
}
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}]")),
}
}
}
#[derive(Debug)]
pub(crate) struct ExpressionTakeRule;
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.dtype().is_boolean() {
return Ok(None);
}
let labels = label_bound_tree(
expression,
|node| match node.as_scalar() {
Some(scalar_fn) => (
false,
scalar_fn.signature().is_strict(),
scalar_fn.signature().is_infallible(),
),
None => (true, true, true),
},
|acc, &child| (acc.0 | child.0, acc.1 & child.1, acc.2 & child.2),
);
let (references_root, is_strict, is_infallible) = labels
.get(&ExactBoundExpr(expression.clone()))
.copied()
.unwrap_or((false, false, false));
if !references_root || !is_strict || !is_infallible {
return Ok(None);
}
let values = EvalPlan::try_new(expression.clone(), child.values()?)?.into_plan();
Ok(Some(TakePlan::new(child.codes()?, values).into_plan()))
}
}