Skip to content
Merged
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
35 changes: 35 additions & 0 deletions crates/quiver/tests/quiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,41 @@ fn column_desc_is_parameterized_by_the_logical_type() {
assert!(MAYBE_AGE.metadata.is_empty());
}

#[test]
fn column_descs_are_copy_debug_and_comparable() {
// `Utf8` and friends derive nothing, so a descriptor over one only compiles
// here because the impls put no bound on `L`:
const NAME: quiver::ColumnDesc<Utf8> = quiver::ColumnDesc::new("Typed", "name");
const OTHER: quiver::ColumnDesc<Option<i64>> = quiver::ColumnDesc::new("Other", "maybe_age");
assert_eq!(NAME, NAME);
assert!(format!("{NAME:?}").contains("name"));

let desc: quiver::ColumnDesc<Option<i64>> = Typed::COLUMN_MAYBE_AGE;
let copy = desc; // `Copy`, so `desc` stays usable below.
assert_eq!(desc, copy);
assert_eq!(desc, Typed::COLUMN_MAYBE_AGE);

// Same name and type, different owner → not equal.
assert_ne!(desc, OTHER);

// Declared metadata takes part in the comparison.
assert_ne!(
Annotated::COLUMN_CHUNK_ID,
quiver::ColumnDesc::<quiver::FixedSizeBinary<16>>::new("Annotated", "chunk_id")
);

// `Debug` names the fields, and skips the `PhantomData`:
let shown = format!("{desc:?}");
assert!(shown.contains("maybe_age"), "{shown}");
assert!(!shown.contains("PhantomData"), "{shown}");

// `DynColumnDesc` gets the same treatment:
let dynamic = desc.to_dyn();
assert_eq!(dynamic, desc.to_dyn());
assert_ne!(dynamic, OTHER.to_dyn());
assert!(format!("{dynamic:?}").contains("maybe_age"));
}

/// All columns required: unlike `Typed`, this gets `empty_record_batch`.
#[derive(Quiver)]
struct AllRequired {
Expand Down
44 changes: 43 additions & 1 deletion crates/quiver_types/src/column_desc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,47 @@ pub struct ColumnDesc<L: LogicalType> {
_marker: PhantomData<fn() -> L>,
}

// Hand-written rather than derived: `#[derive]` would put the trait's own bound
// on `L`, but a descriptor holds no `L` — only a `PhantomData<fn() -> L>`, which
// is `Copy`, `Eq`, and `Debug` whatever `L` is.
impl<L: LogicalType> Clone for ColumnDesc<L> {
fn clone(&self) -> Self {
*self
}
}

impl<L: LogicalType> Copy for ColumnDesc<L> {}

impl<L: LogicalType> std::fmt::Debug for ColumnDesc<L> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
record_type,
name,
metadata,
_marker,
} = self;
f.debug_struct("ColumnDesc")
.field("record_type", record_type)
.field("name", name)
.field("metadata", metadata)
.finish()
}
}

impl<L: LogicalType> PartialEq for ColumnDesc<L> {
fn eq(&self, other: &Self) -> bool {
let Self {
record_type,
name,
metadata,
_marker,
} = self;
*record_type == other.record_type && *name == other.name && *metadata == other.metadata
}
}

impl<L: LogicalType> Eq for ColumnDesc<L> {}

impl<L: LogicalType> ColumnDesc<L> {
/// Describes the column `name` of `record_type`, which labels the errors
/// (the name of the `#[derive(Quiver)]` struct, when there is one).
Expand Down Expand Up @@ -142,7 +183,7 @@ impl<L: LogicalType> ColumnDesc<L> {
/// The declared [`metadata`](ColumnDesc::metadata) is dropped, since
/// [`DynColumnDesc`] does not carry any.
#[must_use]
pub const fn to_dyn(&self) -> DynColumnDesc {
pub const fn to_dyn(self) -> DynColumnDesc {
DynColumnDesc::new(self.record_type, self.name)
}
}
Expand All @@ -162,6 +203,7 @@ impl<L: LogicalType> From<&ColumnDesc<L>> for DynColumnDesc {
/// The untyped counterpart of [`ColumnDesc`]: it extracts a
/// [`DynColumn`] (field plus array), with no datatype or
/// nullability validation.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DynColumnDesc {
/// The name of the `#[derive(Quiver)]` struct, for error messages.
pub record_type: &'static str,
Expand Down
Loading