Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions docs/developer-guide/internals/vtables.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,21 @@ registered in the session by their ID.

For a concept `Foo`, the components are organized into these files:

| File | Contains |
|---------------|-----------------------------------------------------------------------|
| `vtable.rs` | `FooVTable` trait definition |
| `typed.rs` | `Foo<V>` data struct, inherent methods, `Deref` impl |
| `erased.rs` | `FooRef` struct, `DynFoo` sealed trait, blanket impl |
| `plugin.rs` | `FooPlugin` trait, registration |
| `matcher.rs` | Downcasting helpers (`is`, `as_`, `as_opt`, pattern matching traits) |
| File | Contains |
|--------------|----------------------------------------------------------------------|
| `vtable.rs` | `FooVTable` trait definition |
| `typed.rs` | `Foo<V>` data struct, inherent methods, `Deref` impl |
| `erased.rs` | `FooRef` struct, `DynFoo` sealed trait, blanket impl |
| `plugin.rs` | `FooPlugin` trait, registration |
| `matcher.rs` | Downcasting helpers (`is`, `as_`, `as_opt`, pattern matching traits) |

For Array encodings, each encoding has its own module (e.g. `arrays/primitive/`):

| File | Contains |
|-------------------------|-------------------------------------------------------------|
| `arrays/foo/mod.rs` | `V::Array` associated type, encoding-specific methods on it |
| `arrays/foo/vtable.rs` | `ArrayVTable` impl for this encoding |
| `arrays/foo/compute/` | Compute kernel implementations |
| File | Contains |
|------------------------|-------------------------------------------------------------|
| `arrays/foo/mod.rs` | `V::Array` associated type, encoding-specific methods on it |
| `arrays/foo/vtable.rs` | `ArrayVTable` impl for this encoding |
| `arrays/foo/compute/` | Compute kernel implementations |

## Example: ExtDType

Expand Down
13 changes: 5 additions & 8 deletions vortex-array/src/dtype/extension/erased.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ use crate::dtype::extension::ExtId;
use crate::dtype::extension::ExtVTable;
use crate::dtype::extension::Matcher;
use crate::dtype::extension::typed::DynExtDType;
use crate::dtype::extension::typed::ExtDTypeInner;
use crate::scalar::ScalarValue;

/// A type-erased extension dtype.
Expand Down Expand Up @@ -127,12 +126,10 @@ impl ExtDTypeRef {
/// Downcast to the concrete [`ExtDType`].
///
/// Returns `Err(self)` if the downcast fails.
pub fn try_downcast<V: ExtVTable>(self) -> Result<ExtDType<V>, ExtDTypeRef> {
if self.0.as_any().is::<ExtDTypeInner<V>>() {
// SAFETY: type matches and ExtDTypeInner<V> is the only implementor
let ptr = Arc::into_raw(self.0) as *const ExtDTypeInner<V>;
let inner = unsafe { Arc::from_raw(ptr) };
Ok(ExtDType(inner))
pub fn try_downcast<V: ExtVTable>(self) -> Result<Arc<ExtDType<V>>, ExtDTypeRef> {
if self.0.as_any().is::<ExtDType<V>>() {
let ptr = Arc::into_raw(self.0) as *const ExtDType<V>;
Ok(unsafe { Arc::from_raw(ptr) })
} else {
Err(self)
}
Expand All @@ -143,7 +140,7 @@ impl ExtDTypeRef {
/// # Panics
///
/// Panics if the downcast fails.
pub fn downcast<V: ExtVTable>(self) -> ExtDType<V> {
pub fn downcast<V: ExtVTable>(self) -> Arc<ExtDType<V>> {
self.try_downcast::<V>()
.map_err(|this| {
vortex_err!(
Expand Down
8 changes: 4 additions & 4 deletions vortex-array/src/dtype/extension/matcher.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

use crate::dtype::extension::ExtDType;
use crate::dtype::extension::ExtDTypeRef;
use crate::dtype::extension::ExtVTable;
use crate::dtype::extension::typed::ExtDTypeInner;

/// A trait for matching extension dtypes.
pub trait Matcher {
Expand All @@ -23,13 +23,13 @@ impl<V: ExtVTable> Matcher for V {
type Match<'a> = &'a V::Metadata;

fn matches(item: &ExtDTypeRef) -> bool {
item.0.as_any().is::<ExtDTypeInner<V>>()
item.0.as_any().is::<ExtDType<V>>()
}

fn try_match<'a>(item: &'a ExtDTypeRef) -> Option<Self::Match<'a>> {
item.0
.as_any()
.downcast_ref::<ExtDTypeInner<V>>()
.map(|inner| &inner.metadata)
.downcast_ref::<ExtDType<V>>()
.map(|inner| inner.metadata())
}
}
4 changes: 2 additions & 2 deletions vortex-array/src/dtype/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ pub type ExtId = arcref::ArcRef<str>;
/// Private module to seal [`typed::DynExtDType`].
mod sealed {
use crate::dtype::extension::ExtVTable;
use crate::dtype::extension::typed::ExtDTypeInner;
use crate::dtype::extension::typed::ExtDType;

/// Marker trait to prevent external implementations of [`super::typed::DynExtDType`].
pub(crate) trait Sealed {}

/// This can be the **only** implementor for [`super::typed::DynExtDType`].
impl<V: ExtVTable> Sealed for ExtDTypeInner<V> {}
impl<V: ExtVTable> Sealed for ExtDType<V> {}
}
58 changes: 22 additions & 36 deletions vortex-array/src/dtype/extension/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ use crate::scalar::ScalarValue;
/// [`try_with_vtable()`]: ExtDType::try_with_vtable
/// [`erased()`]: ExtDType::erased
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtDType<V: ExtVTable>(pub(super) Arc<ExtDTypeInner<V>>);
pub struct ExtDType<V: ExtVTable> {
/// The extension dtype vtable.
vtable: V,
/// The extension dtype metadata.
metadata: V::Metadata,
/// The underlying storage dtype.
storage_dtype: DType,
}

/// Convenience implementation for zero-sized VTables (or VTables that implement `Default`).
impl<V: ExtVTable + Default> ExtDType<V> {
Expand All @@ -49,60 +56,43 @@ impl<V: ExtVTable> ExtDType<V> {
metadata: V::Metadata,
storage_dtype: DType,
) -> VortexResult<Self> {
vtable.validate_dtype(&metadata, &storage_dtype)?;

Ok(Self(Arc::new(ExtDTypeInner::<V> {
let this = Self {
vtable,
metadata,
storage_dtype,
})))
};

this.vtable.validate_dtype(&this)?;

Ok(this)
}

/// Returns the identifier of the extension type.
pub fn id(&self) -> ExtId {
self.0.vtable.id()
self.vtable.id()
}

/// Returns the vtable of the extension type.
pub fn vtable(&self) -> &V {
&self.0.vtable
&self.vtable
}

/// Returns the metadata of the extension type.
pub fn metadata(&self) -> &V::Metadata {
&self.0.metadata
&self.metadata
}

/// Returns the storage dtype of the extension type.
pub fn storage_dtype(&self) -> &DType {
&self.0.storage_dtype
&self.storage_dtype
}

/// Erase the concrete type information, returning a type-erased extension dtype.
pub fn erased(self) -> ExtDTypeRef {
ExtDTypeRef(self.0)
ExtDTypeRef(Arc::new(self))
}
}

// ---------------------------------------------------------------------------
// Private inner struct + sealed trait
// ---------------------------------------------------------------------------

/// The private inner representation of an extension dtype, pairing a vtable with its metadata
/// and storage dtype.
///
/// This is the sole implementor of [`DynExtDType`], enabling [`ExtDTypeRef`] to safely downcast
/// back to the concrete vtable type via [`Any`].
#[derive(Debug, PartialEq, Eq, Hash)]
pub(super) struct ExtDTypeInner<V: ExtVTable> {
/// The extension dtype vtable.
pub(super) vtable: V,
/// The extension dtype metadata.
pub(super) metadata: V::Metadata,
/// The underlying storage dtype.
pub(super) storage_dtype: DType,
}

/// An object-safe, sealed trait encapsulating the behavior for extension dtypes.
///
/// This provides type-erased access to the extension dtype's identity, storage dtype, and
Expand Down Expand Up @@ -135,7 +125,7 @@ pub(super) trait DynExtDType: 'static + Send + Sync + super::sealed::Sealed {
-> fmt::Result;
}

impl<V: ExtVTable> DynExtDType for ExtDTypeInner<V> {
impl<V: ExtVTable> DynExtDType for ExtDType<V> {
fn as_any(&self) -> &dyn Any {
self
}
Expand Down Expand Up @@ -186,19 +176,15 @@ impl<V: ExtVTable> DynExtDType for ExtDTypeInner<V> {
}

fn value_validate(&self, storage_value: &ScalarValue) -> VortexResult<()> {
self.vtable
.validate_scalar_value(&self.metadata, &self.storage_dtype, storage_value)
self.vtable.validate_scalar_value(self, storage_value)
}

fn value_display(
&self,
f: &mut fmt::Formatter<'_>,
storage_value: &ScalarValue,
) -> fmt::Result {
match self
.vtable
.unpack_native(&self.metadata, &self.storage_dtype, storage_value)
{
match self.vtable.unpack_native(self, storage_value) {
Ok(native) => fmt::Display::fmt(&native, f),
Err(_) => write!(
f,
Expand Down
13 changes: 5 additions & 8 deletions vortex-array/src/dtype/extension/vtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::hash::Hash;

use vortex_error::VortexResult;

use crate::dtype::DType;
use crate::dtype::extension::ExtDType;
use crate::dtype::extension::ExtId;
use crate::scalar::ScalarValue;

Expand Down Expand Up @@ -36,7 +36,7 @@ pub trait ExtVTable: 'static + Sized + Send + Sync + Clone + Debug + Eq + Hash {
fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult<Self::Metadata>;

/// Validate that the given storage type is compatible with this extension type.
fn validate_dtype(&self, metadata: &Self::Metadata, storage_dtype: &DType) -> VortexResult<()>;
fn validate_dtype(&self, ext_dtype: &ExtDType<Self>) -> VortexResult<()>;

// Methods related to the extension scalar values.

Expand All @@ -49,12 +49,10 @@ pub trait ExtVTable: 'static + Sized + Send + Sync + Clone + Debug + Eq + Hash {
/// Returns an error if the storage [`ScalarValue`] is not compatible with the extension type.
fn validate_scalar_value(
&self,
metadata: &Self::Metadata,
storage_dtype: &DType,
ext_dtype: &ExtDType<Self>,
storage_value: &ScalarValue,
) -> VortexResult<()> {
self.unpack_native(metadata, storage_dtype, storage_value)
.map(|_| ())
self.unpack_native(ext_dtype, storage_value).map(|_| ())
}

/// Validate and unpack a native value from the storage [`ScalarValue`].
Expand All @@ -68,8 +66,7 @@ pub trait ExtVTable: 'static + Sized + Send + Sync + Clone + Debug + Eq + Hash {
/// Returns an error if the storage [`ScalarValue`] is not compatible with the extension type.
fn unpack_native<'a>(
&self,
metadata: &'a Self::Metadata,
storage_dtype: &'a DType,
ext_dtype: &'a ExtDType<Self>,
storage_value: &'a ScalarValue,
) -> VortexResult<Self::NativeValue<'a>>;
}
9 changes: 5 additions & 4 deletions vortex-array/src/extension/datetime/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,13 @@ impl ExtVTable for Date {
TimeUnit::try_from(tag)
}

fn validate_dtype(&self, metadata: &Self::Metadata, storage_dtype: &DType) -> VortexResult<()> {
fn validate_dtype(&self, ext_dtype: &ExtDType<Self>) -> VortexResult<()> {
let metadata = ext_dtype.metadata();
let ptype = date_ptype(metadata)
.ok_or_else(|| vortex_err!("Date type does not support time unit {}", metadata))?;

vortex_ensure!(
storage_dtype.as_ptype() == ptype,
ext_dtype.storage_dtype().as_ptype() == ptype,
"Date storage dtype for {} must be {}",
metadata,
ptype
Expand All @@ -107,10 +108,10 @@ impl ExtVTable for Date {

fn unpack_native(
&self,
metadata: &Self::Metadata,
_storage_dtype: &DType,
ext_dtype: &ExtDType<Self>,
storage_value: &ScalarValue,
) -> VortexResult<Self::NativeValue<'_>> {
let metadata = ext_dtype.metadata();
match metadata {
TimeUnit::Milliseconds => Ok(DateValue::Milliseconds(
storage_value.as_primitive().cast::<i64>()?,
Expand Down
10 changes: 5 additions & 5 deletions vortex-array/src/extension/datetime/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,13 @@ impl ExtVTable for Time {
TimeUnit::try_from(tag)
}

fn validate_dtype(&self, metadata: &Self::Metadata, storage_dtype: &DType) -> VortexResult<()> {
fn validate_dtype(&self, ext_dtype: &ExtDType<Self>) -> VortexResult<()> {
let metadata = ext_dtype.metadata();
let ptype = time_ptype(metadata)
.ok_or_else(|| vortex_err!("Time type does not support time unit {}", metadata))?;

vortex_ensure!(
storage_dtype.as_ptype() == ptype,
ext_dtype.storage_dtype().as_ptype() == ptype,
"Time storage dtype for {} must be {}",
metadata,
ptype
Expand All @@ -108,13 +109,12 @@ impl ExtVTable for Time {

fn unpack_native(
&self,
metadata: &Self::Metadata,
_storage_dtype: &DType,
ext_dtype: &ExtDType<Self>,
storage_value: &ScalarValue,
) -> VortexResult<Self::NativeValue<'_>> {
let length_of_time = storage_value.as_primitive().cast::<i64>()?;

let (span, value) = match *metadata {
let (span, value) = match *ext_dtype.metadata() {
TimeUnit::Seconds => {
let v = i32::try_from(length_of_time)
.map_err(|e| vortex_err!("Time seconds value out of i32 range: {e}"))?;
Expand Down
12 changes: 4 additions & 8 deletions vortex-array/src/extension/datetime/timestamp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,24 +169,20 @@ impl ExtVTable for Timestamp {
})
}

fn validate_dtype(
&self,
_metadata: &Self::Metadata,
storage_dtype: &DType,
) -> VortexResult<()> {
fn validate_dtype(&self, ext_dtype: &ExtDType<Self>) -> VortexResult<()> {
vortex_ensure!(
matches!(storage_dtype, DType::Primitive(PType::I64, _)),
matches!(ext_dtype.storage_dtype(), DType::Primitive(PType::I64, _)),
"Timestamp storage dtype must be i64"
);
Ok(())
}

fn unpack_native<'a>(
&self,
metadata: &'a Self::Metadata,
_storage_dtype: &'a DType,
ext_dtype: &'a ExtDType<Self>,
storage_value: &'a ScalarValue,
) -> VortexResult<Self::NativeValue<'a>> {
let metadata = ext_dtype.metadata();
let ts_value = storage_value.as_primitive().cast::<i64>()?;
let tz = metadata.tz.as_ref();

Expand Down
Loading
Loading