spacetimedb_table/
read_column.rs

1//! Provides a trait [`ReadColumn`] for extracting a single column from a [`crate::table::RowRef`].
2//! This is desirable as frequently, e.g. when evaluating filtered queries,
3//! we are interested in only a single column (or a small set of columns),
4//! and would like to avoid the allocation required by a `ProductValue`.
5
6use crate::{
7    bflatn_from,
8    indexes::{PageOffset, Size},
9    layout::{AlgebraicTypeLayout, PrimitiveType, ProductTypeElementLayout, VarLenType},
10    table::RowRef,
11};
12use spacetimedb_sats::{
13    algebraic_value::{ser::ValueSerializer, Packed},
14    i256,
15    sum_value::SumTag,
16    u256, AlgebraicType, AlgebraicValue, ArrayValue, ProductType, ProductValue, SumValue,
17};
18use std::{cell::Cell, mem};
19use thiserror::Error;
20
21#[derive(Error, Debug)]
22pub enum TypeError {
23    #[error(
24        "Attempt to read column {} of a product with only {} columns of type {:?}",
25        desired,
26        found.elements.len(),
27        found,
28    )]
29    IndexOutOfBounds { desired: usize, found: ProductType },
30    #[error("Attempt to read a column at type `{desired}`, but the column's type is {found:?}")]
31    WrongType {
32        desired: &'static str,
33        found: AlgebraicType,
34    },
35}
36
37/// Types which can be stored in a column of a row,
38/// and can be extracted directly from a row.
39///
40/// # Safety
41///
42/// The implementor must define `is_compatible_type` to return `true` only for `AlgebraicTypeLayout`s
43/// for which `unchecked_read_column` is safe.
44/// The provided `read_column` method uses `is_compatible_type` to detect type errors,
45/// and calls `unchecked_read_column` if `is_compatible_type` returns true.
46pub unsafe trait ReadColumn: Sized {
47    /// Is `ty` compatible with `Self`?
48    ///
49    /// The definition of "compatible" here is left to the implementor,
50    /// to be defined by `Self::is_compatible_type`.
51    ///
52    /// For most types,"compatibility" will mean that each Rust type which implements `ReadColumn`
53    /// has exactly one corresponding [`AlgebraicTypeLayout`] which represents it,
54    /// and the column in `table.row_layout` must be of that type.
55    ///
56    /// Notable exceptions are [`AlgebraicValue`], [`ProductValue`] and [`SumValue`].
57    /// Any `ProductTypeLayout` is compatible with `ProductValue`,
58    /// any `SumTypeLayout` is compatible with `SumValue`,
59    /// and any `AlgebraicTypeLayout` at all is compatible with `AlgebraicValue`.
60    fn is_compatible_type(ty: &AlgebraicTypeLayout) -> bool;
61
62    /// Extract a value of type `Self` from the row pointed to by `row_ref`
63    /// which is stored in the column defined by `layout`.
64    ///
65    /// # Safety
66    ///
67    /// `layout` must appear as a column in the `table.row_layout.product().elements`,
68    /// *not* to a nested field of a column which is a product or sum value.
69    /// That column must have the same layout as `layout`.
70    /// This restriction may be loosened in the future.
71    ///
72    /// Assuming that the `row_ref` refers to a properly-aligned row,
73    /// adding the `layout.offset` must result in a properly-aligned value of that compatible type.
74    ///
75    /// `layout.ty` must be compatible with `Self`.
76    /// The definition of "compatible" here is left to the implementor,
77    /// to be defined by `Self::is_compatible_type`.
78    ///
79    /// For most types,"compatibility" will mean that each Rust type which implements `ReadColumn`
80    /// has exactly one corresponding [`AlgebraicTypeLayout`] which represents it,
81    /// and the column in `table.row_layout` must be of that type.
82    ///
83    /// Notable exceptions are [`AlgebraicValue`], [`ProductValue`] and [`SumValue`].
84    /// Any `ProductTypeLayout` is compatible with `ProductValue`,
85    /// any `SumTypeLayout` is compatible with `SumValue`,
86    /// and any `AlgebraicTypeLayout` at all is compatible with `AlgebraicValue`.
87    ///
88    /// # Notes for implementors
89    ///
90    /// Implementors may depend on all of the above safety requirements,
91    /// and on the validity of the `row_ref`.
92    /// Assuming all of the above safety requirements are met and the `row_ref` refers to a valid row,
93    /// this method *must never* invoke Undefined Behavior.
94    ///
95    /// Implementors should carefully study the BFLATN format.
96    /// Currently BFLATN lacks a normative specification,
97    /// so implementors should read the definitions in [`layout.rs`], [`bflatn_to.rs`] and [`bflatn_from.rs`].
98    /// A few highlights are included here:
99    ///
100    /// - Variable-length columns, i.e. `AlgebraicType::String`, `AlgebraicType::Array` and `AlgebraicType::Map`
101    ///   are stored within the row as [`crate::var_len::VarLenRef`s],
102    ///   which refer to an intrusive linked list of 62-byte "granules",
103    ///   allocated separately in a space starting from the end of the page.
104    ///   Strings are stored as UTF-8 bytes; all other var-len types are stored as BSATN-encoded bytes.
105    ///
106    /// - Fixed-length columns, i.e. all types not listed above as variable-length,
107    ///   are stored inline at a known offset.
108    ///   Their layout generally matches the C ABI on an x86_64 Linux machine,
109    ///   with the notable exception of sum types, since the C ABI doesn't define a layout for sums.
110    ///
111    /// - Fixed-length columns are stored in order, with padding between to ensure proper alignment.
112    ///
113    /// - Primitive (non-compound) fixed-length types, i.e. integers, floats and booleans,
114    ///   have alignment equal to their size.
115    ///
116    /// - Integers are stored little-endian.
117    ///
118    /// - Floats are stored by bitwise converting to integers as per IEEE-754,
119    ///   then storing those integers little-endian.
120    ///
121    /// - Booleans are stored as `u8`, i.e. bytes, restricted to the values `0` and `1`.
122    ///
123    /// - Products store their elements in order, with padding between to ensure proper alignment.
124    ///
125    /// - The first element of a product has offset 0.
126    ///
127    /// - The alignment of a product is the maximum alignment of its elements,
128    ///   or 1 for the empty product.
129    ///
130    /// - The size of a product is the number of bytes required to store its elements, including padding,
131    ///   plus trailing padding bytes so that the size is a multiple of the alignment.
132    ///
133    /// - Sums store their payload at offset 0, followed by a 1-byte tag.
134    ///
135    /// - The alignment of a sum is the maximum alignment of its variants' payloads.
136    ///
137    /// - The size of a sum is the maximum size of its variants' payloads, plus 1 (the tag),
138    ///   plus trailing padding bytes so that the size is a multiple of the alignment.
139    ///
140    /// - The offset of a sum's tag bit is the maximum size of its variants' payloads.
141    unsafe fn unchecked_read_column(row_ref: RowRef<'_>, layout: &ProductTypeElementLayout) -> Self;
142
143    /// Check that the `idx`th column of the row type stored by `row_ref` is compatible with `Self`,
144    /// and read the value of that column from `row_ref`.
145    fn read_column(row_ref: RowRef<'_>, idx: usize) -> Result<Self, TypeError> {
146        let layout = row_ref.row_layout().product();
147
148        // Look up the `ProductTypeElementLayout` of the requested column,
149        // or return an error on an out-of-bounds index.
150        let col = layout.elements.get(idx).ok_or_else(|| TypeError::IndexOutOfBounds {
151            desired: idx,
152            found: layout.product_type(),
153        })?;
154
155        // Check that the requested column is of the expected type.
156        if !Self::is_compatible_type(&col.ty) {
157            return Err(TypeError::WrongType {
158                desired: std::any::type_name::<Self>(),
159                found: col.ty.algebraic_type(),
160            });
161        }
162
163        Ok(unsafe {
164            // SAFETY:
165            // - We trust that the `row_ref.table` knows its own layout,
166            //   and we've derived our type and layout info from it,
167            //   so they are correct.
168            // - We trust `Self::is_compatible_type`, and it returned `true`,
169            //   so the column must be of appropriate type.
170            Self::unchecked_read_column(row_ref, col)
171        })
172    }
173}
174
175unsafe impl ReadColumn for bool {
176    fn is_compatible_type(ty: &AlgebraicTypeLayout) -> bool {
177        matches!(ty, AlgebraicTypeLayout::Primitive(PrimitiveType::Bool))
178    }
179
180    unsafe fn unchecked_read_column(row_ref: RowRef<'_>, layout: &ProductTypeElementLayout) -> Self {
181        debug_assert!(Self::is_compatible_type(&layout.ty));
182
183        let (page, offset) = row_ref.page_and_offset();
184        let col_offset = offset + PageOffset(layout.offset);
185
186        let data = page.get_row_data(col_offset, Size(mem::size_of::<Self>() as u16));
187        let data: *const bool = data.as_ptr().cast();
188        // SAFETY: We trust that the `row_ref` refers to a valid, initialized row,
189        // and that the `offset_in_bytes` refers to a column of type `Bool` within that row.
190        // A valid row can never have a column of an invalid value,
191        // and no byte in `Page.row_data` is ever uninit,
192        // so `data` must be initialized as either 0 or 1.
193        unsafe { *data }
194    }
195}
196
197macro_rules! impl_read_column_number {
198    ($primitive_type:ident => $native_type:ty) => {
199        unsafe impl ReadColumn for $native_type {
200            fn is_compatible_type(ty: &AlgebraicTypeLayout) -> bool {
201                matches!(ty, AlgebraicTypeLayout::Primitive(PrimitiveType::$primitive_type))
202            }
203
204            unsafe fn unchecked_read_column(
205                row_ref: RowRef<'_>,
206                layout: &ProductTypeElementLayout,
207            ) -> Self {
208                debug_assert!(Self::is_compatible_type(&layout.ty));
209
210                let (page, offset) = row_ref.page_and_offset();
211                let col_offset = offset + PageOffset(layout.offset);
212
213                let data = page.get_row_data(col_offset, Size(mem::size_of::<Self>() as u16));
214                let data: Result<[u8; mem::size_of::<Self>()], _> = data.try_into();
215                // SAFETY: `<[u8; N] as TryFrom<&[u8]>` succeeds if and only if the slice's length is `N`.
216                // We used `mem::size_of::<Self>()` as both the length of the slice and the array,
217                // so we know them to be equal.
218                let data = unsafe { data.unwrap_unchecked() };
219
220                Self::from_le_bytes(data)
221            }
222        }
223    };
224
225    ($($primitive_type:ident => $native_type:ty);* $(;)*) => {
226        $(impl_read_column_number!($primitive_type => $native_type);)*
227    };
228}
229
230impl_read_column_number! {
231    I8 => i8;
232    U8 => u8;
233    I16 => i16;
234    U16 => u16;
235    I32 => i32;
236    U32 => u32;
237    I64 => i64;
238    U64 => u64;
239    I128 => i128;
240    U128 => u128;
241    I256 => i256;
242    U256 => u256;
243    F32 => f32;
244    F64 => f64;
245}
246
247unsafe impl ReadColumn for AlgebraicValue {
248    fn is_compatible_type(_ty: &AlgebraicTypeLayout) -> bool {
249        true
250    }
251    unsafe fn unchecked_read_column(row_ref: RowRef<'_>, layout: &ProductTypeElementLayout) -> Self {
252        let curr_offset = Cell::new(layout.offset as usize);
253        let blob_store = row_ref.blob_store();
254        let (page, page_offset) = row_ref.page_and_offset();
255        let fixed_bytes = page.get_row_data(page_offset, row_ref.row_layout().size());
256
257        // SAFETY:
258        // 1. Our requirements on `row_ref` and `layout` mean that the column is valid at `layout`.
259        // 2. As a result of the above, all `VarLenRef`s in the column are valid.
260        // 3. Our requirements on `offset_in_bytes` mean that our `curr_offset` is valid.
261        let res = unsafe {
262            bflatn_from::serialize_value(ValueSerializer, fixed_bytes, page, blob_store, &curr_offset, &layout.ty)
263        };
264
265        debug_assert!(res.is_ok());
266
267        // SAFETY: `ValueSerializer` is infallible.
268        unsafe { res.unwrap_unchecked() }
269    }
270}
271
272macro_rules! impl_read_column_via_av {
273    ($av_pattern:pat => $into_method:ident => $native_type:ty) => {
274        unsafe impl ReadColumn for $native_type {
275            fn is_compatible_type(ty: &AlgebraicTypeLayout) -> bool {
276                matches!(ty, $av_pattern)
277            }
278
279            unsafe fn unchecked_read_column(
280                row_ref: RowRef<'_>,
281                layout: &ProductTypeElementLayout,
282            ) -> Self {
283                debug_assert!(Self::is_compatible_type(&layout.ty));
284
285                // SAFETY:
286                // - Any layout is valid for `AlgebraicValue`, including our `layout`.
287                // - Forward requirements on `offset_in_bytes`.
288                let av = unsafe { AlgebraicValue::unchecked_read_column(row_ref, layout) };
289
290                let res = av.$into_method();
291
292                debug_assert!(res.is_ok());
293
294                // SAFETY: We trust that the value `row_ref + offset_in_bytes` is of type `layout`,
295                // and that `layout` is the layout for `Self`,
296                // so the `av` above must be a `Self`.
297                unsafe { res.unwrap_unchecked() }
298            }
299        }
300    };
301
302    ($($av_pattern:pat => $into_method:ident => $native_type:ty);* $(;)*) => {
303        $(impl_read_column_via_av!($av_pattern => $into_method => $native_type);)*
304    };
305}
306
307impl_read_column_via_av! {
308    AlgebraicTypeLayout::VarLen(VarLenType::String) => into_string => Box<str>;
309    AlgebraicTypeLayout::VarLen(VarLenType::Array(_)) => into_array => ArrayValue;
310    AlgebraicTypeLayout::Sum(_) => into_sum => SumValue;
311    AlgebraicTypeLayout::Product(_) => into_product => ProductValue;
312}
313
314macro_rules! impl_read_column_via_from {
315    ($($base:ty => $target:ty);* $(;)*) => {
316        $(
317            unsafe impl ReadColumn for $target {
318                fn is_compatible_type(ty: &AlgebraicTypeLayout) -> bool {
319                    <$base>::is_compatible_type(ty)
320                }
321
322                unsafe fn unchecked_read_column(row_ref: RowRef<'_>, layout: &ProductTypeElementLayout) -> Self {
323                    // SAFETY: We use `$base`'s notion of compatible types, so we can forward promises.
324                    <$target>::from(unsafe { <$base>::unchecked_read_column(row_ref, layout) })
325                }
326            }
327        )*
328    };
329}
330
331impl_read_column_via_from! {
332    u16 => spacetimedb_primitives::ColId;
333    u32 => spacetimedb_primitives::TableId;
334    u32 => spacetimedb_primitives::IndexId;
335    u32 => spacetimedb_primitives::ConstraintId;
336    u32 => spacetimedb_primitives::SequenceId;
337    u32 => spacetimedb_primitives::ScheduleId;
338    u128 => Packed<u128>;
339    i128 => Packed<i128>;
340    u256 => Box<u256>;
341    i256 => Box<i256>;
342}
343
344/// SAFETY: `is_compatible_type` only returns true for sum types,
345/// and any sum value stores the tag first in BFLATN.
346unsafe impl ReadColumn for SumTag {
347    fn is_compatible_type(ty: &AlgebraicTypeLayout) -> bool {
348        matches!(ty, AlgebraicTypeLayout::Sum(_))
349    }
350
351    unsafe fn unchecked_read_column(row_ref: RowRef<'_>, layout: &ProductTypeElementLayout) -> Self {
352        debug_assert!(Self::is_compatible_type(&layout.ty));
353
354        let (page, offset) = row_ref.page_and_offset();
355        let col_offset = offset + PageOffset(layout.offset);
356
357        let data = page.get_row_data(col_offset, Size(1));
358        let data: Result<[u8; 1], _> = data.try_into();
359        // SAFETY: `<[u8; 1] as TryFrom<&[u8]>` succeeds if and only if the slice's length is `1`.
360        // We used `1` as both the length of the slice and the array, so we know them to be equal.
361        let [data] = unsafe { data.unwrap_unchecked() };
362
363        Self(data)
364    }
365}
366
367#[cfg(test)]
368mod test {
369    use super::*;
370    use crate::blob_store::HashMapBlobStore;
371    use crate::table::test::table;
372    use proptest::{prelude::*, prop_assert_eq, proptest, test_runner::TestCaseResult};
373    use spacetimedb_sats::{product, proptest::generate_typed_row};
374
375    proptest! {
376        #![proptest_config(ProptestConfig::with_cases(if cfg!(miri) { 8 } else { 2048 }))]
377
378        #[test]
379        /// Test that `AlgebraicValue::read_column` returns expected values.
380        ///
381        /// That is, test that, for any row type and any row value,
382        /// inserting the row, then doing `AlgebraicValue::read_column` on each column of the row
383        /// returns the expected value.
384        fn read_column_same_value((ty, val) in generate_typed_row()) {
385            let mut blob_store = HashMapBlobStore::default();
386            let mut table = table(ty);
387
388            let (_, row_ref) = table.insert(&mut blob_store, &val).unwrap();
389
390            for (idx, orig_col_value) in val.into_iter().enumerate() {
391                let read_col_value = row_ref.read_col::<AlgebraicValue>(idx).unwrap();
392                prop_assert_eq!(orig_col_value, read_col_value);
393            }
394        }
395
396        #[test]
397        /// Test that trying to read a column at a type more specific than `AlgebraicValue`
398        /// which does not match the actual column type
399        /// returns an appropriate error.
400        fn read_column_wrong_type((ty, val) in generate_typed_row()) {
401            let mut blob_store = HashMapBlobStore::default();
402            let mut table = table(ty.clone());
403
404            let (_, row_ref) = table.insert(&mut blob_store, &val).unwrap();
405
406            for (idx, col_ty) in ty.elements.iter().enumerate() {
407                assert_wrong_type_error::<u8>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U8)?;
408                assert_wrong_type_error::<i8>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I8)?;
409                assert_wrong_type_error::<u16>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U16)?;
410                assert_wrong_type_error::<i16>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I16)?;
411                assert_wrong_type_error::<u32>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U32)?;
412                assert_wrong_type_error::<i32>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I32)?;
413                assert_wrong_type_error::<u64>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U64)?;
414                assert_wrong_type_error::<i64>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I64)?;
415                assert_wrong_type_error::<u128>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U128)?;
416                assert_wrong_type_error::<i128>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I128)?;
417                assert_wrong_type_error::<u256>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U256)?;
418                assert_wrong_type_error::<i256>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I256)?;
419                assert_wrong_type_error::<f32>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::F32)?;
420                assert_wrong_type_error::<f64>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::F64)?;
421                assert_wrong_type_error::<bool>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::Bool)?;
422                assert_wrong_type_error::<Box<str>>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::String)?;
423            }
424        }
425
426        #[test]
427        /// Test that trying to read a column which does not exist,
428        /// i.e. with an out-of-bounds index,
429        /// returns an appropriate error.
430        fn read_column_out_of_bounds((ty, val) in generate_typed_row()) {
431            let mut blob_store = HashMapBlobStore::default();
432            let mut table = table(ty.clone());
433
434            let (_, row_ref) = table.insert(&mut blob_store, &val).unwrap();
435
436            let oob = ty.elements.len();
437
438            match row_ref.read_col::<AlgebraicValue>(oob) {
439                Err(TypeError::IndexOutOfBounds { desired, found }) => {
440                    prop_assert_eq!(desired, oob);
441                    // Constructing a table changes the `ProductType` by adding column names
442                    // if the type has `None` for its element names,
443                    // so we can't blindly `prop_assert_eq!(found, ty)`.
444                    // Instead, check that they have the same number of elements
445                    // and that each element has the same type.
446                    prop_assert_eq!(found.elements.len(), ty.elements.len());
447                    for (found_col, ty_col) in found.elements.iter().zip(ty.elements.iter()) {
448                        prop_assert_eq!(&found_col.algebraic_type, &ty_col.algebraic_type);
449                    }
450                }
451                Err(e) => panic!("Expected TypeError::IndexOutOfBounds but found {:?}", e),
452                Ok(val) => panic!("Expected error but found Ok({:?})", val),
453            }
454        }
455    }
456
457    /// Assert, if and only if `col_ty` is not `correct_col_ty`,
458    /// that `row_ref.read_col::<Col>(col_idx)` returns a `TypeError::WrongType`.
459    ///
460    /// If `col_ty == correct_col_ty`, do nothing.
461    fn assert_wrong_type_error<Col: ReadColumn + PartialEq + std::fmt::Debug>(
462        row_ref: RowRef<'_>,
463        col_idx: usize,
464        col_ty: &AlgebraicType,
465        correct_col_ty: AlgebraicType,
466    ) -> TestCaseResult {
467        if col_ty != &correct_col_ty {
468            match row_ref.read_col::<Col>(col_idx) {
469                Err(TypeError::WrongType { desired, found }) => {
470                    prop_assert_eq!(desired, std::any::type_name::<Col>());
471                    prop_assert_eq!(&found, col_ty);
472                }
473                Err(e) => panic!("Expected TypeError::WrongType but found {:?}", e),
474                Ok(val) => panic!("Expected error but found Ok({:?})", val),
475            }
476        }
477        Ok(())
478    }
479
480    /// Define a test or tests which constructs a row containing a known value of a known type,
481    /// then uses `ReadColumn::read_column` to extract that type as a native type,
482    /// e.g. a Rust integer,
483    /// and asserts that the extracted value is as expected.
484    macro_rules! test_read_column_primitive {
485        ($name:ident { $algebraic_type:expr => $rust_type:ty = $val:expr }) => {
486            #[test]
487            fn $name() {
488                let mut blob_store = HashMapBlobStore::default();
489                let mut table = table(ProductType::from_iter([$algebraic_type]));
490
491                let val: $rust_type = $val;
492                let (_, row_ref) = table.insert(&mut blob_store, &product![val.clone()]).unwrap();
493
494                assert_eq!(val, row_ref.read_col::<$rust_type>(0).unwrap());
495            }
496        };
497
498
499        ($($name:ident { $algebraic_type:expr => $rust_type:ty = $val:expr };)*) => {
500            $(test_read_column_primitive! {
501                $name { $algebraic_type => $rust_type = $val }
502            })*
503        }
504    }
505
506    test_read_column_primitive! {
507        read_column_i8 { AlgebraicType::I8 => i8 = i8::MAX };
508        read_column_u8 { AlgebraicType::U8 => u8 = 0xa5 };
509        read_column_i16 { AlgebraicType::I16 => i16 = i16::MAX };
510        read_column_u16 { AlgebraicType::U16 => u16 = 0xa5a5 };
511        read_column_i32 { AlgebraicType::I32 => i32 = i32::MAX };
512        read_column_u32 { AlgebraicType::U32 => u32 = 0xa5a5a5a5 };
513        read_column_i64 { AlgebraicType::I64 => i64 = i64::MAX };
514        read_column_u64 { AlgebraicType::U64 => u64 = 0xa5a5a5a5_a5a5a5a5 };
515        read_column_i128 { AlgebraicType::I128 => i128 = i128::MAX };
516        read_column_u128 { AlgebraicType::U128 => u128 = 0xa5a5a5a5_a5a5a5a5_a5a5a5a5_a5a5a5a5 };
517        read_column_i256 { AlgebraicType::I256 => i256 = i256::MAX };
518        read_column_u256 { AlgebraicType::U256 => u256 =
519            u256::from_words(
520                0xa5a5a5a5_a5a5a5a5_a5a5a5a5_a5a5a5a5,
521                0xa5a5a5a5_a5a5a5a5_a5a5a5a5_a5a5a5a5
522            )
523        };
524
525        read_column_f32 { AlgebraicType::F32 => f32 = 1.0 };
526        read_column_f64 { AlgebraicType::F64 => f64 = 1.0 };
527
528        read_column_bool { AlgebraicType::Bool => bool = true };
529
530        read_column_empty_string { AlgebraicType::String => Box<str> = "".into() };
531
532        // Use a short string which fits in a single granule.
533        read_column_short_string { AlgebraicType::String => Box<str> = "short string".into() };
534
535        // Use a medium-sized string which takes multiple granules.
536        read_column_medium_string { AlgebraicType::String => Box<str> = "medium string.".repeat(16).into() };
537
538        // Use a long string which will hit the blob store.
539        read_column_long_string { AlgebraicType::String => Box<str> = "long string. ".repeat(2048).into() };
540
541        read_sum_value_plain { AlgebraicType::simple_enum(["a", "b"].into_iter()) => SumValue = SumValue::new_simple(1) };
542        read_sum_tag_plain { AlgebraicType::simple_enum(["a", "b"].into_iter()) => SumTag = SumTag(1) };
543    }
544
545    #[test]
546    fn read_sum_tag_from_sum_with_payload() {
547        let algebraic_type = AlgebraicType::sum([("a", AlgebraicType::U8), ("b", AlgebraicType::U16)]);
548
549        let mut blob_store = HashMapBlobStore::default();
550        let mut table = table(ProductType::from([algebraic_type]));
551
552        let val = SumValue::new(1, 42u16);
553        let (_, row_ref) = table.insert(&mut blob_store, &product![val.clone()]).unwrap();
554
555        assert_eq!(val.tag, row_ref.read_col::<SumTag>(0).unwrap().0);
556    }
557}