use std::marker::PhantomData;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_ensure;
use vortex_error::vortex_ensure_eq;
use super::RowVisitor;
use super::check::assert_deferred_visit_contract;
use super::check::assert_owned_visit_contract;
use super::check::assert_sink_visit_contract;
use super::check::validate_owned_visit;
use super::check::validate_sink_visit;
use super::row_visitor::private;
use crate::ArrayRef;
use crate::IntoArray;
use crate::arrays::ExtensionArray;
use crate::dtype::DType;
use crate::dtype::Nullability;
use crate::dtype::extension::ExtDTypeRef;
use crate::scalar_fn::unstable::row::ElementTuple;
use crate::scalar_fn::unstable::row::FailureEvidence;
use crate::scalar_fn::unstable::row::IndexedElementTuple;
use crate::scalar_fn::unstable::row::OutputElement;
use crate::scalar_fn::unstable::row::OutputSink;
use crate::scalar_fn::unstable::row::RowFn;
use crate::scalar_fn::unstable::row::SinkResult;
pub(crate) struct BatchPlanner<'a, F: RowFn> {
dtypes: &'a [DType],
output_dtype: Option<DType>,
function: PhantomData<F>,
}
impl<'a, F: RowFn> BatchPlanner<'a, F> {
pub(crate) fn new(dtypes: &'a [DType]) -> Self {
Self {
dtypes,
output_dtype: None,
function: PhantomData,
}
}
}
impl<F: RowFn> private::Sealed for BatchPlanner<'_, F> {}
impl<F: RowFn> RowVisitor for BatchPlanner<'_, F> {
type VisitResult = BatchPlan;
fn with_output_dtype(mut self, dtype: DType) -> Self {
self.output_dtype = Some(dtype);
self
}
fn visit_prepared<Args, Out, Prepared>(
self,
_prepare: impl FnOnce(Args::ConstElems<'_>) -> Prepared,
_apply: impl Fn(&Prepared, Args::Elems<'_>) -> Out,
) -> VortexResult<Self::VisitResult>
where
Args: IndexedElementTuple,
Out: OutputElement,
{
const { assert_owned_visit_contract::<F, Args, Out>() };
BatchPlan::new(
validate_owned_visit::<Args, Out>(self.dtypes)?,
self.output_dtype,
RowPolicy::for_owned_output::<Args>(),
)
}
fn visit_prepared_into<Args, Sink, Prepared, ApplyResult>(
self,
params: Sink::Params,
_prepare: impl FnOnce(Args::ConstElems<'_>) -> Prepared,
_apply: impl Fn(&Prepared, Args::Elems<'_>, Sink::Row<'_>) -> ApplyResult,
) -> VortexResult<Self::VisitResult>
where
Args: ElementTuple,
Sink: OutputSink,
ApplyResult: SinkResult<WriteToken = Sink::WriteToken>,
{
const { assert_sink_visit_contract::<F, Args, ApplyResult>() };
BatchPlan::new(
validate_sink_visit::<Args, Sink>(self.dtypes, ¶ms)?,
self.output_dtype,
RowPolicy::for_sink::<Args, ApplyResult>(),
)
}
fn visit_prepared_deferred<Args, Out, Prepared, Fail>(
self,
_prepare: impl FnOnce(Args::ConstElems<'_>) -> Prepared,
_apply: impl Fn(&Prepared, Args::Elems<'_>) -> (Out, Fail),
_finish_failure: impl FnOnce(Fail) -> VortexResult<()>,
) -> VortexResult<Self::VisitResult>
where
Args: IndexedElementTuple,
Out: OutputElement,
Fail: FailureEvidence,
{
const { assert_deferred_visit_contract::<F, Args, Out, Fail>() };
BatchPlan::new(
validate_owned_visit::<Args, Out>(self.dtypes)?,
self.output_dtype,
RowPolicy::for_deferred_output::<Args>(),
)
}
}
pub(crate) struct BatchPlan {
storage_dtype: DType,
output_label: Option<ExtDTypeRef>,
policy: RowPolicy,
}
impl BatchPlan {
pub(crate) fn new(
storage_dtype: DType,
output_dtype: Option<DType>,
policy: RowPolicy,
) -> VortexResult<Self> {
let output_label = match output_dtype {
Some(output_dtype) => validate_output_label(&storage_dtype, output_dtype)?,
None => None,
};
Ok(Self {
storage_dtype,
output_label,
policy,
})
}
pub(crate) fn storage_dtype(&self) -> &DType {
&self.storage_dtype
}
pub(crate) fn output_dtype(&self) -> DType {
match &self.output_label {
Some(output_label) => DType::Extension(output_label.clone()),
None => self.storage_dtype.clone(),
}
}
pub(crate) fn policy(&self) -> RowPolicy {
self.policy
}
pub(crate) fn result_dtype(&self, args: &[DType]) -> DType {
let output_dtype = self.output_dtype();
let nullability =
output_dtype.nullability() | Nullability::from(args.iter().any(DType::is_nullable));
output_dtype.with_nullability(nullability)
}
pub(crate) fn relabel_output(&self, values: ArrayRef) -> VortexResult<ArrayRef> {
let Some(output_label) = &self.output_label else {
return Ok(values);
};
let output_label = output_label.with_nullability(values.dtype().nullability());
Ok(ExtensionArray::try_new(output_label, values)?.into_array())
}
pub(crate) fn ensure_reproduced_by(&self, actual: &Self) -> VortexResult<()> {
vortex_ensure_eq!(
actual.policy,
self.policy,
"row dispatch must select the planned nullable execution policy: planned {:?}, got {:?}",
self.policy,
actual.policy,
);
vortex_ensure_eq!(
actual.storage_dtype,
self.storage_dtype,
"row dispatch must select the planned storage dtype: planned {}, got {}",
self.storage_dtype,
actual.storage_dtype,
);
vortex_ensure!(
actual.output_label == self.output_label,
"row dispatch must declare the planned output dtype: planned {}, got {}",
self.output_dtype(),
actual.output_dtype(),
);
Ok(())
}
}
fn validate_output_label(
storage_dtype: &DType,
output_dtype: DType,
) -> VortexResult<Option<ExtDTypeRef>> {
vortex_ensure!(
!output_dtype.is_nullable(),
"a declared row output dtype must be non-nullable, got {output_dtype}",
);
if output_dtype == *storage_dtype {
return Ok(None);
}
let DType::Extension(output_label) = output_dtype else {
vortex_bail!(
"a declared row output dtype must be an extension dtype over the storage dtype \
{storage_dtype}, got {output_dtype}",
);
};
vortex_ensure_eq!(
*output_label.storage_dtype(),
*storage_dtype,
"a declared row extension output dtype must store {storage_dtype}, got {}",
output_label.storage_dtype(),
);
Ok(Some(output_label))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum RowPolicy {
Dense,
DenseWithRetry,
ValidOnly,
}
impl RowPolicy {
pub(crate) const fn for_owned_output<Args: ElementTuple>() -> Self {
if Args::DENSE_SAFE && Args::DECODE_INFALLIBLE {
Self::Dense
} else {
Self::ValidOnly
}
}
pub(crate) const fn for_deferred_output<Args: ElementTuple>() -> Self {
if Args::DENSE_SAFE && Args::DECODE_INFALLIBLE {
Self::DenseWithRetry
} else {
Self::ValidOnly
}
}
pub(crate) const fn for_sink<Args: ElementTuple, ApplyResult: SinkResult>() -> Self {
if Args::DENSE_SAFE && Args::DECODE_INFALLIBLE && ApplyResult::INFALLIBLE {
Self::Dense
} else {
Self::ValidOnly
}
}
}