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::table::test::table;
371    use crate::{blob_store::HashMapBlobStore, page_pool::PagePool};
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 pool = PagePool::new_for_test();
386            let mut blob_store = HashMapBlobStore::default();
387            let mut table = table(ty);
388
389            let (_, row_ref) = table.insert(&pool, &mut blob_store, &val).unwrap();
390
391            for (idx, orig_col_value) in val.into_iter().enumerate() {
392                let read_col_value = row_ref.read_col::<AlgebraicValue>(idx).unwrap();
393                prop_assert_eq!(orig_col_value, read_col_value);
394            }
395        }
396
397        #[test]
398        /// Test that trying to read a column at a type more specific than `AlgebraicValue`
399        /// which does not match the actual column type
400        /// returns an appropriate error.
401        fn read_column_wrong_type((ty, val) in generate_typed_row()) {
402            let pool = PagePool::new_for_test();
403            let mut blob_store = HashMapBlobStore::default();
404            let mut table = table(ty.clone());
405
406            let (_, row_ref) = table.insert(&pool, &mut blob_store, &val).unwrap();
407
408            for (idx, col_ty) in ty.elements.iter().enumerate() {
409                assert_wrong_type_error::<u8>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U8)?;
410                assert_wrong_type_error::<i8>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I8)?;
411                assert_wrong_type_error::<u16>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U16)?;
412                assert_wrong_type_error::<i16>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I16)?;
413                assert_wrong_type_error::<u32>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U32)?;
414                assert_wrong_type_error::<i32>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I32)?;
415                assert_wrong_type_error::<u64>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U64)?;
416                assert_wrong_type_error::<i64>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I64)?;
417                assert_wrong_type_error::<u128>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U128)?;
418                assert_wrong_type_error::<i128>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I128)?;
419                assert_wrong_type_error::<u256>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::U256)?;
420                assert_wrong_type_error::<i256>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::I256)?;
421                assert_wrong_type_error::<f32>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::F32)?;
422                assert_wrong_type_error::<f64>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::F64)?;
423                assert_wrong_type_error::<bool>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::Bool)?;
424                assert_wrong_type_error::<Box<str>>(row_ref, idx, &col_ty.algebraic_type, AlgebraicType::String)?;
425            }
426        }
427
428        #[test]
429        /// Test that trying to read a column which does not exist,
430        /// i.e. with an out-of-bounds index,
431        /// returns an appropriate error.
432        fn read_column_out_of_bounds((ty, val) in generate_typed_row()) {
433            let pool = PagePool::new_for_test();
434            let mut blob_store = HashMapBlobStore::default();
435            let mut table = table(ty.clone());
436
437            let (_, row_ref) = table.insert(&pool, &mut blob_store, &val).unwrap();
438
439            let oob = ty.elements.len();
440
441            match row_ref.read_col::<AlgebraicValue>(oob) {
442                Err(TypeError::IndexOutOfBounds { desired, found }) => {
443                    prop_assert_eq!(desired, oob);
444                    // Constructing a table changes the `ProductType` by adding column names
445                    // if the type has `None` for its element names,
446                    // so we can't blindly `prop_assert_eq!(found, ty)`.
447                    // Instead, check that they have the same number of elements
448                    // and that each element has the same type.
449                    prop_assert_eq!(found.elements.len(), ty.elements.len());
450                    for (found_col, ty_col) in found.elements.iter().zip(ty.elements.iter()) {
451                        prop_assert_eq!(&found_col.algebraic_type, &ty_col.algebraic_type);
452                    }
453                }
454                Err(e) => panic!("Expected TypeError::IndexOutOfBounds but found {:?}", e),
455                Ok(val) => panic!("Expected error but found Ok({:?})", val),
456            }
457        }
458    }
459
460    /// Assert, if and only if `col_ty` is not `correct_col_ty`,
461    /// that `row_ref.read_col::<Col>(col_idx)` returns a `TypeError::WrongType`.
462    ///
463    /// If `col_ty == correct_col_ty`, do nothing.
464    fn assert_wrong_type_error<Col: ReadColumn + PartialEq + std::fmt::Debug>(
465        row_ref: RowRef<'_>,
466        col_idx: usize,
467        col_ty: &AlgebraicType,
468        correct_col_ty: AlgebraicType,
469    ) -> TestCaseResult {
470        if col_ty != &correct_col_ty {
471            match row_ref.read_col::<Col>(col_idx) {
472                Err(TypeError::WrongType { desired, found }) => {
473                    prop_assert_eq!(desired, std::any::type_name::<Col>());
474                    prop_assert_eq!(&found, col_ty);
475                }
476                Err(e) => panic!("Expected TypeError::WrongType but found {:?}", e),
477                Ok(val) => panic!("Expected error but found Ok({:?})", val),
478            }
479        }
480        Ok(())
481    }
482
483    /// Define a test or tests which constructs a row containing a known value of a known type,
484    /// then uses `ReadColumn::read_column` to extract that type as a native type,
485    /// e.g. a Rust integer,
486    /// and asserts that the extracted value is as expected.
487    macro_rules! test_read_column_primitive {
488        ($name:ident { $algebraic_type:expr => $rust_type:ty = $val:expr }) => {
489            #[test]
490            fn $name() {
491                let pool = PagePool::new_for_test();
492                let mut blob_store = HashMapBlobStore::default();
493                let mut table = table(ProductType::from_iter([$algebraic_type]));
494
495                let val: $rust_type = $val;
496                let (_, row_ref) = table.insert(&pool, &mut blob_store, &product![val.clone()]).unwrap();
497
498                assert_eq!(val, row_ref.read_col::<$rust_type>(0).unwrap());
499            }
500        };
501
502
503        ($($name:ident { $algebraic_type:expr => $rust_type:ty = $val:expr };)*) => {
504            $(test_read_column_primitive! {
505                $name { $algebraic_type => $rust_type = $val }
506            })*
507        }
508    }
509
510    test_read_column_primitive! {
511        read_column_i8 { AlgebraicType::I8 => i8 = i8::MAX };
512        read_column_u8 { AlgebraicType::U8 => u8 = 0xa5 };
513        read_column_i16 { AlgebraicType::I16 => i16 = i16::MAX };
514        read_column_u16 { AlgebraicType::U16 => u16 = 0xa5a5 };
515        read_column_i32 { AlgebraicType::I32 => i32 = i32::MAX };
516        read_column_u32 { AlgebraicType::U32 => u32 = 0xa5a5a5a5 };
517        read_column_i64 { AlgebraicType::I64 => i64 = i64::MAX };
518        read_column_u64 { AlgebraicType::U64 => u64 = 0xa5a5a5a5_a5a5a5a5 };
519        read_column_i128 { AlgebraicType::I128 => i128 = i128::MAX };
520        read_column_u128 { AlgebraicType::U128 => u128 = 0xa5a5a5a5_a5a5a5a5_a5a5a5a5_a5a5a5a5 };
521        read_column_i256 { AlgebraicType::I256 => i256 = i256::MAX };
522        read_column_u256 { AlgebraicType::U256 => u256 =
523            u256::from_words(
524                0xa5a5a5a5_a5a5a5a5_a5a5a5a5_a5a5a5a5,
525                0xa5a5a5a5_a5a5a5a5_a5a5a5a5_a5a5a5a5
526            )
527        };
528
529        read_column_f32 { AlgebraicType::F32 => f32 = 1.0 };
530        read_column_f64 { AlgebraicType::F64 => f64 = 1.0 };
531
532        read_column_bool { AlgebraicType::Bool => bool = true };
533
534        read_column_empty_string { AlgebraicType::String => Box<str> = "".into() };
535
536        // Use a short string which fits in a single granule.
537        read_column_short_string { AlgebraicType::String => Box<str> = "short string".into() };
538
539        // Use a medium-sized string which takes multiple granules.
540        read_column_medium_string { AlgebraicType::String => Box<str> = "medium string.".repeat(16).into() };
541
542        // Use a long string which will hit the blob store.
543        read_column_long_string { AlgebraicType::String => Box<str> = "long string. ".repeat(2048).into() };
544
545        read_sum_value_plain { AlgebraicType::simple_enum(["a", "b"].into_iter()) => SumValue = SumValue::new_simple(1) };
546        read_sum_tag_plain { AlgebraicType::simple_enum(["a", "b"].into_iter()) => SumTag = SumTag(1) };
547    }
548
549    #[test]
550    fn read_sum_tag_from_sum_with_payload() {
551        let algebraic_type = AlgebraicType::sum([("a", AlgebraicType::U8), ("b", AlgebraicType::U16)]);
552
553        let pool = PagePool::new_for_test();
554        let mut blob_store = HashMapBlobStore::default();
555        let mut table = table(ProductType::from([algebraic_type]));
556
557        let val = SumValue::new(1, 42u16);
558        let (_, row_ref) = table.insert(&pool, &mut blob_store, &product![val.clone()]).unwrap();
559
560        assert_eq!(val.tag, row_ref.read_col::<SumTag>(0).unwrap().0);
561    }
562}