use std::collections::HashMap;
use crate::array::ScalarValue;
use crate::error::{Error, Result};
use crate::format::constants::{
COLUMN_LAYOUT_CONSTANT, COLUMN_LAYOUT_DICTIONARY, COLUMN_LAYOUT_PLAIN,
COLUMN_LAYOUT_RUN_LENGTH, STREAM_KIND_DICTIONARY_LENGTHS, STREAM_KIND_DICTIONARY_VALUES,
STREAM_KIND_INDICES, STREAM_KIND_LENGTHS, STREAM_KIND_RUN_LENGTHS, STREAM_KIND_RUN_VALUES,
STREAM_KIND_VALUES, TRANSFORM_BIT_PACKED, TRANSFORM_BOOLEAN_RLE, TRANSFORM_BYTE_STREAM_SPLIT,
TRANSFORM_DELTA, TRANSFORM_DELTA_OF_DELTA, TRANSFORM_FRAME_OF_REFERENCE, TRANSFORM_RAW,
};
use crate::schema::LogicalType;
use super::super::buffer::bitmap_bytes;
const DISTINCT_VALUE_LIMIT: usize = 4_096;
const DISTINCT_BYTE_LIMIT: usize = 1 << 20;
const _: () = assert!(DISTINCT_VALUE_LIMIT <= u16::MAX as usize + 1);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(super) enum ValueEncoding {
Raw,
BitPacked,
BooleanRle,
FrameOfReference,
Delta,
DeltaOfDelta,
ByteStreamSplit,
Constant,
Dictionary,
RunLength,
}
impl ValueEncoding {
pub(super) fn rank(self) -> u8 {
match self {
Self::Raw => 0,
Self::BitPacked => 1,
Self::BooleanRle => 2,
Self::FrameOfReference => 3,
Self::Delta => 4,
Self::DeltaOfDelta => 5,
Self::ByteStreamSplit => 6,
Self::Constant => 7,
Self::Dictionary => 8,
Self::RunLength => 9,
}
}
}
#[derive(Debug)]
pub(super) struct CandidateStream {
pub(super) kind: u16,
pub(super) transform: u16,
pub(super) element_count: u64,
pub(super) bytes: Vec<u8>,
}
#[derive(Debug)]
pub(super) struct ValueCandidate {
pub(super) layout: u16,
pub(super) streams: Vec<CandidateStream>,
}
#[derive(Debug)]
pub(super) enum PreparedValues {
Bool(Vec<bool>),
Fixed {
width: usize,
signed: Option<bool>,
bytes: Vec<u8>,
},
Variable {
bytes: Vec<u8>,
lengths: Vec<u32>,
},
}
#[derive(Debug)]
pub(super) struct ValueProfile {
pub(super) count: usize,
pub(super) constant: bool,
pub(super) run_lengths: Vec<u32>,
pub(super) max_run_length: u32,
pub(super) dictionary: Option<DictionaryProfile>,
pub(super) numeric: Option<NumericProfile>,
}
#[derive(Debug)]
pub(super) struct DictionaryProfile {
pub(super) values: Vec<Vec<u8>>,
pub(super) indices: Vec<u16>,
}
#[derive(Debug)]
pub(super) struct NumericProfile {
pub(super) minimum: i128,
pub(super) bit_packed_bits: Option<u8>,
pub(super) frame_of_reference_bits: Option<u8>,
pub(super) delta_bits: Option<u8>,
pub(super) delta_of_delta_bits: Option<u8>,
}
pub(super) fn prepare<'a, I>(
logical_type: &LogicalType,
values: I,
) -> Result<(PreparedValues, ValueProfile)>
where
I: IntoIterator<Item = Result<ScalarValue<'a>>>,
{
let mut prepared = empty_values(logical_type)?;
for value in values {
push_value(logical_type, &mut prepared, value?)?;
}
let profile = profile(&prepared)?;
Ok((prepared, profile))
}
fn empty_values(logical_type: &LogicalType) -> Result<PreparedValues> {
let (width, signed) = match logical_type {
LogicalType::Bool => return Ok(PreparedValues::Bool(Vec::new())),
LogicalType::Utf8 | LogicalType::Categorical { .. } | LogicalType::Binary => {
return Ok(PreparedValues::Variable {
bytes: Vec::new(),
lengths: Vec::new(),
});
}
LogicalType::Int8 => (1, Some(true)),
LogicalType::Int16 => (2, Some(true)),
LogicalType::Int32 | LogicalType::Date32 => (4, Some(true)),
LogicalType::Int64 | LogicalType::Decimal { .. } | LogicalType::Timestamp { .. } => {
(8, Some(true))
}
LogicalType::UInt8 => (1, Some(false)),
LogicalType::UInt16 => (2, Some(false)),
LogicalType::UInt32 => (4, Some(false)),
LogicalType::UInt64 => (8, Some(false)),
LogicalType::Float32 => (4, None),
LogicalType::Float64 => (8, None),
LogicalType::FixedBinary { byte_width } => (
usize::try_from(*byte_width)
.map_err(|_| resource("a fixed_binary width this platform can hold"))?,
None,
),
};
if width == 0 {
return Err(invalid("a fixed-width column cannot have zero width"));
}
Ok(PreparedValues::Fixed {
width,
signed,
bytes: Vec::new(),
})
}
fn push_value(
logical_type: &LogicalType,
prepared: &mut PreparedValues,
value: ScalarValue<'_>,
) -> Result<()> {
match (logical_type, prepared, value) {
(LogicalType::Bool, PreparedValues::Bool(values), ScalarValue::Bool(value)) => {
values
.try_reserve(1)
.map_err(|_| resource("boolean values"))?;
values.push(value);
}
(LogicalType::Int8, PreparedValues::Fixed { bytes, .. }, ScalarValue::Int8(value)) => {
push_bytes(bytes, &value.to_le_bytes())?
}
(LogicalType::Int16, PreparedValues::Fixed { bytes, .. }, ScalarValue::Int16(value)) => {
push_bytes(bytes, &value.to_le_bytes())?
}
(LogicalType::Int32, PreparedValues::Fixed { bytes, .. }, ScalarValue::Int32(value)) => {
push_bytes(bytes, &value.to_le_bytes())?
}
(LogicalType::Int64, PreparedValues::Fixed { bytes, .. }, ScalarValue::Int64(value)) => {
push_bytes(bytes, &value.to_le_bytes())?
}
(LogicalType::UInt8, PreparedValues::Fixed { bytes, .. }, ScalarValue::UInt8(value)) => {
push_bytes(bytes, &value.to_le_bytes())?
}
(LogicalType::UInt16, PreparedValues::Fixed { bytes, .. }, ScalarValue::UInt16(value)) => {
push_bytes(bytes, &value.to_le_bytes())?
}
(LogicalType::UInt32, PreparedValues::Fixed { bytes, .. }, ScalarValue::UInt32(value)) => {
push_bytes(bytes, &value.to_le_bytes())?
}
(LogicalType::UInt64, PreparedValues::Fixed { bytes, .. }, ScalarValue::UInt64(value)) => {
push_bytes(bytes, &value.to_le_bytes())?
}
(
LogicalType::Float32,
PreparedValues::Fixed { bytes, .. },
ScalarValue::Float32(value),
) => push_bytes(bytes, &value.to_bits().to_le_bytes())?,
(
LogicalType::Float64,
PreparedValues::Fixed { bytes, .. },
ScalarValue::Float64(value),
) => push_bytes(bytes, &value.to_bits().to_le_bytes())?,
(
LogicalType::Decimal { .. },
PreparedValues::Fixed { bytes, .. },
ScalarValue::Decimal { unscaled, .. },
) => push_bytes(bytes, &unscaled.to_le_bytes())?,
(
LogicalType::Timestamp { .. },
PreparedValues::Fixed { bytes, .. },
ScalarValue::Timestamp { value, .. },
) => push_bytes(bytes, &value.to_le_bytes())?,
(LogicalType::Date32, PreparedValues::Fixed { bytes, .. }, ScalarValue::Date32(value)) => {
push_bytes(bytes, &value.to_le_bytes())?
}
(
LogicalType::FixedBinary { byte_width },
PreparedValues::Fixed { bytes, .. },
ScalarValue::FixedBinary(value),
) => {
if value.len() != *byte_width as usize {
return Err(invalid("fixed_binary value has the wrong width"));
}
push_bytes(bytes, value)?
}
(
LogicalType::Utf8,
PreparedValues::Variable { bytes, lengths },
ScalarValue::Utf8(value),
)
| (
LogicalType::Categorical { .. },
PreparedValues::Variable { bytes, lengths },
ScalarValue::Categorical(value),
) => push_variable(bytes, lengths, value.as_bytes())?,
(
LogicalType::Binary,
PreparedValues::Variable { bytes, lengths },
ScalarValue::Binary(value),
) => push_variable(bytes, lengths, value)?,
_ => return Err(invalid("a dense value does not match its logical type")),
}
Ok(())
}
fn push_bytes(buffer: &mut Vec<u8>, bytes: &[u8]) -> Result<()> {
buffer
.try_reserve(bytes.len())
.map_err(|_| resource("dense value bytes"))?;
buffer.extend_from_slice(bytes);
Ok(())
}
fn push_variable(bytes: &mut Vec<u8>, lengths: &mut Vec<u32>, value: &[u8]) -> Result<()> {
let length =
u32::try_from(value.len()).map_err(|_| invalid("a value length exceeds uint32"))?;
lengths
.try_reserve(1)
.map_err(|_| resource("value lengths"))?;
push_bytes(bytes, value)?;
lengths.push(length);
Ok(())
}
fn profile(values: &PreparedValues) -> Result<ValueProfile> {
let count = values.count();
let mut dictionary = DictionaryBuilder::default();
let mut run_lengths: Vec<u32> = Vec::new();
let mut max_run_length = 0_u32;
let mut previous: Option<&[u8]> = None;
for key in values.keys() {
dictionary.push(key)?;
if previous == Some(key) {
let run = run_lengths
.last_mut()
.expect("a run in progress has a length");
*run += 1;
max_run_length = max_run_length.max(*run);
} else {
run_lengths
.try_reserve(1)
.map_err(|_| resource("the run-length profile"))?;
run_lengths.push(1);
max_run_length = max_run_length.max(1);
previous = Some(key);
}
}
Ok(ValueProfile {
count,
constant: run_lengths.len() <= 1,
run_lengths,
max_run_length,
dictionary: dictionary.finish(),
numeric: values.numeric_profile(),
})
}
#[derive(Default)]
struct DictionaryBuilder {
map: HashMap<Vec<u8>, u16>,
values: Vec<Vec<u8>>,
indices: Vec<u16>,
copied_bytes: usize,
exceeded: bool,
}
impl DictionaryBuilder {
fn push(&mut self, key: &[u8]) -> Result<()> {
if self.exceeded {
return Ok(());
}
if let Some(&index) = self.map.get(key) {
self.indices
.try_reserve(1)
.map_err(|_| resource("dictionary indices"))?;
self.indices.push(index);
return Ok(());
}
if self.values.len() == DISTINCT_VALUE_LIMIT
|| self.copied_bytes.saturating_add(key.len()) > DISTINCT_BYTE_LIMIT
{
self.exceeded = true;
self.map = HashMap::new();
self.values = Vec::new();
self.indices = Vec::new();
return Ok(());
}
let index = u16::try_from(self.values.len()).map_err(|_| resource("a dictionary index"))?;
self.map
.try_reserve(1)
.map_err(|_| resource("the dictionary map"))?;
self.values
.try_reserve(1)
.map_err(|_| resource("dictionary values"))?;
self.indices
.try_reserve(1)
.map_err(|_| resource("dictionary indices"))?;
self.map.insert(key.to_vec(), index);
self.values.push(key.to_vec());
self.copied_bytes = self.copied_bytes.saturating_add(key.len());
self.indices.push(index);
Ok(())
}
fn finish(self) -> Option<DictionaryProfile> {
(!self.exceeded).then_some(DictionaryProfile {
values: self.values,
indices: self.indices,
})
}
}
const FALSE_KEY: [u8; 1] = [0];
const TRUE_KEY: [u8; 1] = [1];
#[derive(Clone)]
enum Keys<'a> {
Bool(std::slice::Iter<'a, bool>),
Fixed(std::slice::ChunksExact<'a, u8>),
Variable {
bytes: &'a [u8],
lengths: std::slice::Iter<'a, u32>,
offset: usize,
},
}
impl<'a> Iterator for Keys<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
match self {
Self::Bool(values) => values.next().map(|value| {
if *value {
&TRUE_KEY[..]
} else {
&FALSE_KEY[..]
}
}),
Self::Fixed(chunks) => chunks.next(),
Self::Variable {
bytes,
lengths,
offset,
} => {
let length = usize::try_from(*lengths.next()?).ok()?;
let start = *offset;
let end = start.checked_add(length)?;
*offset = end;
bytes.get(start..end)
}
}
}
}
impl PreparedValues {
fn as_bools(&self) -> Option<&[bool]> {
match self {
Self::Bool(values) => Some(values),
_ => None,
}
}
fn count(&self) -> usize {
match self {
Self::Bool(values) => values.len(),
Self::Fixed { width, bytes, .. } => bytes.len() / *width,
Self::Variable { lengths, .. } => lengths.len(),
}
}
fn keys(&self) -> Keys<'_> {
match self {
Self::Bool(values) => Keys::Bool(values.iter()),
Self::Fixed { width, bytes, .. } => Keys::Fixed(bytes.chunks_exact(*width)),
Self::Variable { bytes, lengths } => Keys::Variable {
bytes,
lengths: lengths.iter(),
offset: 0,
},
}
}
fn numbers(&self) -> Option<impl Iterator<Item = i128> + Clone + '_> {
let Self::Fixed {
width,
signed: Some(signed),
bytes,
} = self
else {
return None;
};
let signed = *signed;
Some(
bytes
.chunks_exact(*width)
.map(move |chunk| read_number(chunk, signed)),
)
}
fn numeric_profile(&self) -> Option<NumericProfile> {
let mut numbers = self.numbers()?;
let Some(first) = numbers.next() else {
return Some(NumericProfile {
minimum: 0,
bit_packed_bits: None,
frame_of_reference_bits: None,
delta_bits: None,
delta_of_delta_bits: None,
});
};
let mut minimum = first;
let mut maximum = first;
let mut nonnegative = first >= 0;
let mut value_bits = bit_width_of(first);
let mut delta_ok = true;
let mut delta_bits = 0_u8;
let mut delta_of_delta_ok = true;
let mut delta_of_delta_bits = 0_u8;
let mut previous = first;
let mut previous_delta: Option<i64> = None;
let mut count = 1_usize;
for value in numbers {
count += 1;
minimum = minimum.min(value);
maximum = maximum.max(value);
nonnegative &= value >= 0;
if nonnegative {
value_bits = value_bits.max(bit_width_of(value));
}
match value
.checked_sub(previous)
.and_then(|delta| i64::try_from(delta).ok())
{
Some(delta) => {
delta_bits = delta_bits.max(bit_width(zigzag(delta)));
if let Some(earlier) = previous_delta {
match delta.checked_sub(earlier) {
Some(difference) => {
delta_of_delta_bits =
delta_of_delta_bits.max(bit_width(zigzag(difference)));
}
None => delta_of_delta_ok = false,
}
}
previous_delta = Some(delta);
}
None => {
delta_ok = false;
delta_of_delta_ok = false;
previous_delta = None;
}
}
previous = value;
}
Some(NumericProfile {
minimum,
bit_packed_bits: nonnegative.then_some(value_bits),
frame_of_reference_bits: maximum
.checked_sub(minimum)
.and_then(|span| u64::try_from(span).ok())
.map(bit_width),
delta_bits: delta_ok.then_some(delta_bits),
delta_of_delta_bits: (delta_ok && delta_of_delta_ok && count >= 2)
.then_some(delta_of_delta_bits),
})
}
}
fn read_number(bytes: &[u8], signed: bool) -> i128 {
let mut raw = [0_u8; 16];
raw[..bytes.len()].copy_from_slice(bytes);
let value = i128::from_le_bytes(raw);
if !signed || bytes.is_empty() {
return value;
}
let unused = (raw.len() - bytes.len()) * 8;
(value << unused) >> unused
}
pub(super) fn candidates(
logical_type: &LogicalType,
values: &PreparedValues,
profile: &ValueProfile,
) -> Vec<ValueEncoding> {
supported_candidates(logical_type)
.iter()
.copied()
.filter(|candidate| is_valid(*candidate, logical_type, values, profile))
.collect()
}
pub(super) fn supports(logical_type: &LogicalType, encoding: ValueEncoding) -> bool {
supported_candidates(logical_type).contains(&encoding)
}
fn supported_candidates(logical_type: &LogicalType) -> &'static [ValueEncoding] {
match logical_type {
LogicalType::Bool => &[
ValueEncoding::Raw,
ValueEncoding::Constant,
ValueEncoding::BitPacked,
ValueEncoding::BooleanRle,
],
LogicalType::Int8
| LogicalType::Int16
| LogicalType::Int32
| LogicalType::Int64
| LogicalType::UInt8
| LogicalType::UInt16
| LogicalType::UInt32
| LogicalType::UInt64
| LogicalType::Decimal { .. }
| LogicalType::Date32 => &[
ValueEncoding::Raw,
ValueEncoding::Constant,
ValueEncoding::FrameOfReference,
ValueEncoding::Delta,
ValueEncoding::DeltaOfDelta,
ValueEncoding::Dictionary,
ValueEncoding::RunLength,
ValueEncoding::BitPacked,
],
LogicalType::Timestamp { .. } => &[
ValueEncoding::Raw,
ValueEncoding::FrameOfReference,
ValueEncoding::Delta,
ValueEncoding::DeltaOfDelta,
],
LogicalType::Float32 | LogicalType::Float64 => &[
ValueEncoding::Raw,
ValueEncoding::Constant,
ValueEncoding::Dictionary,
ValueEncoding::RunLength,
ValueEncoding::ByteStreamSplit,
],
LogicalType::Utf8 | LogicalType::Categorical { .. } | LogicalType::Binary => &[
ValueEncoding::Raw,
ValueEncoding::Constant,
ValueEncoding::Dictionary,
ValueEncoding::RunLength,
],
LogicalType::FixedBinary { .. } => &[
ValueEncoding::Raw,
ValueEncoding::Constant,
ValueEncoding::Dictionary,
ValueEncoding::RunLength,
ValueEncoding::ByteStreamSplit,
],
}
}
fn is_valid(
encoding: ValueEncoding,
logical_type: &LogicalType,
values: &PreparedValues,
profile: &ValueProfile,
) -> bool {
if profile.count == 0 {
return false;
}
let numeric = profile.numeric.as_ref();
match encoding {
ValueEncoding::Raw => true,
ValueEncoding::Constant => profile.constant,
ValueEncoding::BitPacked => match values {
PreparedValues::Bool(_) => true,
_ => numeric.is_some_and(|numeric| numeric.bit_packed_bits.is_some()),
},
ValueEncoding::BooleanRle => matches!(values, PreparedValues::Bool(_)),
ValueEncoding::FrameOfReference => {
numeric.is_some_and(|numeric| numeric.frame_of_reference_bits.is_some())
}
ValueEncoding::Delta => numeric.is_some_and(|numeric| numeric.delta_bits.is_some()),
ValueEncoding::DeltaOfDelta => {
numeric.is_some_and(|numeric| numeric.delta_of_delta_bits.is_some())
}
ValueEncoding::ByteStreamSplit => matches!(values, PreparedValues::Fixed { .. }),
ValueEncoding::Dictionary => profile.dictionary.is_some(),
ValueEncoding::RunLength => {
!matches!(logical_type, LogicalType::Bool) && !profile.run_lengths.is_empty()
}
}
}
pub(super) fn estimate(
logical_type: &LogicalType,
values: &PreparedValues,
profile: &ValueProfile,
encoding: ValueEncoding,
) -> Option<Vec<u64>> {
if !is_valid(encoding, logical_type, values, profile) {
return None;
}
let count = u64::try_from(profile.count).ok()?;
let numeric = profile.numeric.as_ref();
let width = match values {
PreparedValues::Fixed { width, .. } => Some(*width as u64),
_ => None,
};
match encoding {
ValueEncoding::Raw => match values {
PreparedValues::Bool(_) => Some(vec![bitmap_bytes(count)]),
PreparedValues::Fixed { bytes, .. } => Some(vec![bytes.len() as u64]),
PreparedValues::Variable { bytes, .. } => {
Some(vec![bytes.len() as u64, count.checked_mul(4)?])
}
},
ValueEncoding::Constant => match values {
PreparedValues::Bool(_) => Some(vec![1]),
PreparedValues::Fixed { width, .. } => Some(vec![*width as u64]),
PreparedValues::Variable { lengths, .. } => Some(vec![u64::from(*lengths.first()?), 4]),
},
ValueEncoding::BitPacked => Some(vec![packed_length(
profile.count,
numeric
.and_then(|numeric| numeric.bit_packed_bits)
.unwrap_or(1),
)]),
ValueEncoding::BooleanRle => Some(vec![
4 + bitmap_bytes(profile.run_lengths.len() as u64)
+ packed_length(
profile.run_lengths.len(),
bit_width(u64::from(profile.max_run_length)),
),
]),
ValueEncoding::FrameOfReference => Some(vec![width?.checked_add(packed_length(
profile.count,
numeric?.frame_of_reference_bits?,
))?]),
ValueEncoding::Delta => {
let packed = if profile.count <= 1 {
0
} else {
packed_length(profile.count - 1, numeric?.delta_bits?)
};
Some(vec![width?.checked_add(packed)?])
}
ValueEncoding::DeltaOfDelta => Some(vec![width?.checked_add(8)?.checked_add(
packed_length(profile.count - 2, numeric?.delta_of_delta_bits?),
)?]),
ValueEncoding::ByteStreamSplit => Some(vec![count.checked_mul(width?)?]),
ValueEncoding::Dictionary => {
let dictionary = profile.dictionary.as_ref()?;
let mut lengths = vec![dictionary_bytes(dictionary)?];
if matches!(values, PreparedValues::Variable { .. }) {
lengths.push(
u64::try_from(dictionary.values.len())
.ok()?
.checked_mul(4)?,
);
}
lengths.push(packed_length(
profile.count,
bit_width(dictionary.values.len().saturating_sub(1) as u64),
));
Some(lengths)
}
ValueEncoding::RunLength => {
let runs = profile.run_lengths.len();
let mut lengths = vec![run_value_bytes(values, profile)?];
if matches!(values, PreparedValues::Variable { .. }) {
lengths.push(u64::try_from(runs).ok()?.checked_mul(4)?);
}
lengths.push(packed_length(
runs,
bit_width(u64::from(profile.max_run_length)),
));
Some(lengths)
}
}
}
fn dictionary_bytes(dictionary: &DictionaryProfile) -> Option<u64> {
dictionary.values.iter().try_fold(0_u64, |total, value| {
total.checked_add(u64::try_from(value.len()).ok()?)
})
}
fn run_value_bytes(values: &PreparedValues, profile: &ValueProfile) -> Option<u64> {
match values {
PreparedValues::Bool(_) => None,
PreparedValues::Fixed { width, .. } => u64::try_from(profile.run_lengths.len())
.ok()?
.checked_mul(*width as u64),
PreparedValues::Variable { lengths, .. } => {
let mut total = 0_u64;
let mut start = 0_usize;
for run in &profile.run_lengths {
total = total.checked_add(u64::from(*lengths.get(start)?))?;
start = start.checked_add(usize::try_from(*run).ok()?)?;
}
Some(total)
}
}
}
pub(super) fn encode(
logical_type: &LogicalType,
values: &PreparedValues,
profile: &ValueProfile,
encoding: ValueEncoding,
) -> Result<ValueCandidate> {
if !is_valid(encoding, logical_type, values, profile) {
return Err(invalid("this candidate does not describe these values"));
}
let streams = match encoding {
ValueEncoding::Raw => raw_streams(values),
ValueEncoding::Constant => constant_streams(values),
ValueEncoding::BitPacked => bit_packed_streams(values, profile),
ValueEncoding::BooleanRle => boolean_rle_streams(values),
ValueEncoding::FrameOfReference => frame_of_reference_streams(values, profile),
ValueEncoding::Delta => delta_streams(values, profile),
ValueEncoding::DeltaOfDelta => delta_of_delta_streams(values, profile),
ValueEncoding::ByteStreamSplit => byte_stream_split_streams(values),
ValueEncoding::Dictionary => dictionary_streams(values, profile),
ValueEncoding::RunLength => run_length_streams(values, profile),
}?;
let layout = match encoding {
ValueEncoding::Constant => COLUMN_LAYOUT_CONSTANT,
ValueEncoding::Dictionary => COLUMN_LAYOUT_DICTIONARY,
ValueEncoding::RunLength => COLUMN_LAYOUT_RUN_LENGTH,
_ => COLUMN_LAYOUT_PLAIN,
};
Ok(ValueCandidate { layout, streams })
}
fn raw_streams(values: &PreparedValues) -> Result<Vec<CandidateStream>> {
match values {
PreparedValues::Bool(values) => Ok(vec![candidate_stream(
STREAM_KIND_VALUES,
TRANSFORM_RAW,
values.len(),
pack_booleans(values),
)?]),
PreparedValues::Fixed { width, bytes, .. } => Ok(vec![candidate_stream(
STREAM_KIND_VALUES,
TRANSFORM_RAW,
bytes.len() / *width,
copied(bytes)?,
)?]),
PreparedValues::Variable { bytes, lengths } => Ok(vec![
candidate_stream(
STREAM_KIND_VALUES,
TRANSFORM_RAW,
lengths.len(),
copied(bytes)?,
)?,
candidate_stream(
STREAM_KIND_LENGTHS,
TRANSFORM_RAW,
lengths.len(),
length_bytes(lengths)?,
)?,
]),
}
}
fn constant_streams(values: &PreparedValues) -> Result<Vec<CandidateStream>> {
let first = values
.keys()
.next()
.ok_or_else(|| invalid("a constant column has no value"))?;
match values {
PreparedValues::Bool(values) => Ok(vec![candidate_stream(
STREAM_KIND_VALUES,
TRANSFORM_RAW,
1,
pack_booleans(&values[..1]),
)?]),
PreparedValues::Fixed { .. } => Ok(vec![candidate_stream(
STREAM_KIND_VALUES,
TRANSFORM_RAW,
1,
copied(first)?,
)?]),
PreparedValues::Variable { lengths, .. } => Ok(vec![
candidate_stream(STREAM_KIND_VALUES, TRANSFORM_RAW, 1, copied(first)?)?,
candidate_stream(
STREAM_KIND_LENGTHS,
TRANSFORM_RAW,
1,
length_bytes(&lengths[..1])?,
)?,
]),
}
}
fn bit_packed_streams(
values: &PreparedValues,
profile: &ValueProfile,
) -> Result<Vec<CandidateStream>> {
let (count, bytes) = match values {
PreparedValues::Bool(values) => {
let width = u8::from(values.iter().any(|value| *value));
let bytes = pack_from(
width,
values.len(),
values.iter().map(|value| u64::from(*value)),
)?;
(values.len(), bytes)
}
_ => {
let numbers = values
.numbers()
.ok_or_else(|| invalid("bit packing does not apply to this value stream"))?;
let width = profile
.numeric
.as_ref()
.and_then(|numeric| numeric.bit_packed_bits)
.ok_or_else(|| invalid("bit packing has no width for this value stream"))?;
let bytes = pack_from(
width,
profile.count,
numbers.map(|value| u64::try_from(value).expect("the profile checked this value")),
)?;
(profile.count, bytes)
}
};
Ok(vec![candidate_stream(
STREAM_KIND_VALUES,
TRANSFORM_BIT_PACKED,
count,
bytes,
)?])
}
fn boolean_rle_streams(values: &PreparedValues) -> Result<Vec<CandidateStream>> {
let values = values
.as_bools()
.ok_or_else(|| invalid("boolean RLE does not apply to this value stream"))?;
Ok(vec![candidate_stream(
STREAM_KIND_VALUES,
TRANSFORM_BOOLEAN_RLE,
values.len(),
boolean_rle(values)?,
)?])
}
fn frame_of_reference_streams(
values: &PreparedValues,
profile: &ValueProfile,
) -> Result<Vec<CandidateStream>> {
let PreparedValues::Fixed { width, signed, .. } = values else {
return Err(invalid(
"frame of reference does not apply to this value stream",
));
};
let signed = signed.ok_or_else(|| invalid("frame of reference needs a numeric column"))?;
let numeric = profile
.numeric
.as_ref()
.ok_or_else(|| invalid("frame of reference has no numeric profile"))?;
let bits = numeric
.frame_of_reference_bits
.ok_or_else(|| invalid("frame-of-reference difference does not fit uint64"))?;
let numbers = values
.numbers()
.ok_or_else(|| invalid("frame of reference has no values"))?;
let base = numeric.minimum;
let mut output = canonical_number(base, *width, signed)?;
let packed = pack_from(
bits,
profile.count,
numbers.map(|value| {
u64::try_from(value.saturating_sub(base)).expect("the profile checked this difference")
}),
)?;
push_bytes(&mut output, &packed)?;
Ok(vec![candidate_stream(
STREAM_KIND_VALUES,
TRANSFORM_FRAME_OF_REFERENCE,
profile.count,
output,
)?])
}
fn delta_streams(values: &PreparedValues, profile: &ValueProfile) -> Result<Vec<CandidateStream>> {
let PreparedValues::Fixed { width, .. } = values else {
return Err(invalid("delta does not apply to this value stream"));
};
let bits = profile
.numeric
.as_ref()
.and_then(|numeric| numeric.delta_bits)
.ok_or_else(|| invalid("a delta does not fit int64"))?;
let mut output = copied(first_value(values, *width)?)?;
if profile.count > 1 {
let numbers = values
.numbers()
.ok_or_else(|| invalid("delta has no values"))?;
let packed = pack_from(
bits,
profile.count - 1,
adjacent_deltas(numbers).map(zigzag),
)?;
push_bytes(&mut output, &packed)?;
}
Ok(vec![candidate_stream(
STREAM_KIND_VALUES,
TRANSFORM_DELTA,
profile.count,
output,
)?])
}
fn delta_of_delta_streams(
values: &PreparedValues,
profile: &ValueProfile,
) -> Result<Vec<CandidateStream>> {
let PreparedValues::Fixed { width, .. } = values else {
return Err(invalid(
"delta-of-delta does not apply to this value stream",
));
};
let bits = profile
.numeric
.as_ref()
.and_then(|numeric| numeric.delta_of_delta_bits)
.ok_or_else(|| invalid("delta-of-delta requires at least two in-range values"))?;
let numbers = values
.numbers()
.ok_or_else(|| invalid("delta-of-delta has no values"))?;
let first_delta = adjacent_deltas(numbers.clone())
.next()
.ok_or_else(|| invalid("delta-of-delta requires at least two values"))?;
let mut output = copied(first_value(values, *width)?)?;
push_bytes(&mut output, &first_delta.to_le_bytes())?;
let packed = pack_from(
bits,
profile.count - 2,
adjacent_deltas(adjacent_deltas(numbers).map(i128::from)).map(zigzag),
)?;
push_bytes(&mut output, &packed)?;
Ok(vec![candidate_stream(
STREAM_KIND_VALUES,
TRANSFORM_DELTA_OF_DELTA,
profile.count,
output,
)?])
}
fn byte_stream_split_streams(values: &PreparedValues) -> Result<Vec<CandidateStream>> {
let PreparedValues::Fixed { width, bytes, .. } = values else {
return Err(invalid(
"byte-stream split does not apply to this value stream",
));
};
let count = bytes.len() / *width;
let mut output = Vec::new();
output
.try_reserve_exact(bytes.len())
.map_err(|_| resource("byte-stream-split values"))?;
for byte in 0..*width {
for value in 0..count {
output.push(bytes[value * *width + byte]);
}
}
Ok(vec![candidate_stream(
STREAM_KIND_VALUES,
TRANSFORM_BYTE_STREAM_SPLIT,
count,
output,
)?])
}
fn dictionary_streams(
values: &PreparedValues,
profile: &ValueProfile,
) -> Result<Vec<CandidateStream>> {
let dictionary = profile
.dictionary
.as_ref()
.ok_or_else(|| invalid("dictionary cardinality exceeded the profiling cap"))?;
let mut streams = vec![candidate_stream(
STREAM_KIND_DICTIONARY_VALUES,
TRANSFORM_RAW,
dictionary.values.len(),
concat(&dictionary.values)?,
)?];
if matches!(values, PreparedValues::Variable { .. }) {
streams.push(candidate_stream(
STREAM_KIND_DICTIONARY_LENGTHS,
TRANSFORM_RAW,
dictionary.values.len(),
value_lengths(&dictionary.values)?,
)?);
}
let bits = bit_width(dictionary.values.len().saturating_sub(1) as u64);
streams.push(candidate_stream(
STREAM_KIND_INDICES,
TRANSFORM_BIT_PACKED,
dictionary.indices.len(),
pack_from(
bits,
dictionary.indices.len(),
dictionary.indices.iter().map(|index| u64::from(*index)),
)?,
)?);
Ok(streams)
}
fn run_length_streams(
values: &PreparedValues,
profile: &ValueProfile,
) -> Result<Vec<CandidateStream>> {
if matches!(values, PreparedValues::Bool(_)) {
return Err(invalid("generic run length does not apply to booleans"));
}
let runs = profile.run_lengths.len();
let mut run_values = Vec::new();
let mut run_value_lengths: Vec<u32> = Vec::new();
run_value_lengths
.try_reserve_exact(runs)
.map_err(|_| resource("run value lengths"))?;
let mut keys = values.keys();
let mut skipped = 0_usize;
for run in &profile.run_lengths {
let key = keys
.nth(skipped)
.ok_or_else(|| invalid("the run lengths do not describe these values"))?;
push_bytes(&mut run_values, key)?;
run_value_lengths.push(
u32::try_from(key.len()).map_err(|_| invalid("a run value exceeds uint32 bytes"))?,
);
skipped = usize::try_from(*run)
.map_err(|_| resource("a run length this platform can hold"))?
.checked_sub(1)
.ok_or_else(|| invalid("a run length is zero"))?;
}
let mut streams = vec![candidate_stream(
STREAM_KIND_RUN_VALUES,
TRANSFORM_RAW,
runs,
run_values,
)?];
if matches!(values, PreparedValues::Variable { .. }) {
streams.push(candidate_stream(
STREAM_KIND_LENGTHS,
TRANSFORM_RAW,
runs,
length_bytes(&run_value_lengths)?,
)?);
}
streams.push(candidate_stream(
STREAM_KIND_RUN_LENGTHS,
TRANSFORM_BIT_PACKED,
runs,
pack_from(
bit_width(u64::from(profile.max_run_length)),
runs,
profile.run_lengths.iter().map(|run| u64::from(*run)),
)?,
)?);
Ok(streams)
}
fn first_value(values: &PreparedValues, width: usize) -> Result<&[u8]> {
match values {
PreparedValues::Fixed { bytes, .. } => bytes
.get(..width)
.ok_or_else(|| invalid("a fixed-width stream has no first value")),
_ => Err(invalid("this stream has no fixed-width first value")),
}
}
fn adjacent_deltas(values: impl Iterator<Item = i128>) -> impl Iterator<Item = i64> {
let mut previous: Option<i128> = None;
values.filter_map(move |value| {
let delta = previous.map(|earlier| {
i64::try_from(value.saturating_sub(earlier))
.expect("the profile checked this difference")
});
previous = Some(value);
delta
})
}
fn zigzag(value: i64) -> u64 {
((value as u64) << 1) ^ ((value >> 63) as u64)
}
fn canonical_number(value: i128, width: usize, signed: bool) -> Result<Vec<u8>> {
if !signed {
let value =
u64::try_from(value).map_err(|_| invalid("unsigned base is negative or too wide"))?;
return Ok(value.to_le_bytes()[..width].to_vec());
}
match width {
1 => Ok(narrow::<i8>(value)?.to_le_bytes().to_vec()),
2 => Ok(narrow::<i16>(value)?.to_le_bytes().to_vec()),
4 => Ok(narrow::<i32>(value)?.to_le_bytes().to_vec()),
8 => Ok(narrow::<i64>(value)?.to_le_bytes().to_vec()),
_ => Err(invalid("invalid canonical signed width")),
}
}
fn narrow<T: TryFrom<i128>>(value: i128) -> Result<T> {
T::try_from(value)
.ok()
.ok_or_else(|| invalid("signed base does not fit its canonical width"))
}
pub(super) fn candidate_stream(
kind: u16,
transform: u16,
element_count: usize,
bytes: Vec<u8>,
) -> Result<CandidateStream> {
Ok(CandidateStream {
kind,
transform,
element_count: u64::try_from(element_count)
.map_err(|_| resource("a stream element count"))?,
bytes,
})
}
fn copied(bytes: &[u8]) -> Result<Vec<u8>> {
let mut output = Vec::new();
output
.try_reserve_exact(bytes.len())
.map_err(|_| resource("copied stream bytes"))?;
output.extend_from_slice(bytes);
Ok(output)
}
fn concat(values: &[Vec<u8>]) -> Result<Vec<u8>> {
let length = values
.iter()
.try_fold(0_usize, |total, value| total.checked_add(value.len()))
.ok_or_else(|| resource("concatenated values"))?;
let mut output = Vec::new();
output
.try_reserve_exact(length)
.map_err(|_| resource("concatenated values"))?;
for value in values {
output.extend_from_slice(value);
}
Ok(output)
}
fn length_bytes(lengths: &[u32]) -> Result<Vec<u8>> {
let byte_count = lengths
.len()
.checked_mul(4)
.ok_or_else(|| resource("value lengths"))?;
let mut output = Vec::new();
output
.try_reserve_exact(byte_count)
.map_err(|_| resource("value lengths"))?;
for length in lengths {
output.extend_from_slice(&length.to_le_bytes());
}
Ok(output)
}
fn value_lengths(values: &[Vec<u8>]) -> Result<Vec<u8>> {
let mut lengths = Vec::new();
lengths
.try_reserve_exact(values.len())
.map_err(|_| resource("value lengths"))?;
for value in values {
lengths.push(
u32::try_from(value.len()).map_err(|_| invalid("a value length exceeds uint32"))?,
);
}
length_bytes(&lengths)
}
pub(super) fn pack_booleans(values: &[bool]) -> Vec<u8> {
let mut output = vec![0; bitmap_bytes(values.len() as u64) as usize];
for (index, value) in values.iter().enumerate() {
if *value {
output[index / 8] |= 1 << (index % 8);
}
}
output
}
fn pack_from(width: u8, count: usize, values: impl Iterator<Item = u64>) -> Result<Vec<u8>> {
let byte_count = 1_usize
.checked_add(count.saturating_mul(usize::from(width)).div_ceil(8))
.ok_or_else(|| resource("a bit-packed stream"))?;
let mut output = Vec::new();
output
.try_reserve_exact(byte_count)
.map_err(|_| resource("a bit-packed stream"))?;
output.push(width);
output.resize(byte_count, 0);
let mut written = 0_usize;
let mut position = 0_usize;
for value in values {
if written == count {
return Err(invalid(
"a bit-packed stream has more values than it declares",
));
}
written += 1;
if width == 0 {
continue;
}
for bit in 0..usize::from(width) {
if value & (1_u64 << bit) != 0 {
let target = position + bit;
output[1 + target / 8] |= 1 << (target % 8);
}
}
position += usize::from(width);
}
if written != count {
return Err(invalid(
"a bit-packed stream has fewer values than it declares",
));
}
Ok(output)
}
pub(super) fn boolean_rle(values: &[bool]) -> Result<Vec<u8>> {
let mut run_values: Vec<bool> = Vec::new();
let mut run_lengths: Vec<u64> = Vec::new();
for value in values {
if run_values.last() == Some(value) {
*run_lengths.last_mut().expect("a run value has a length") += 1;
continue;
}
run_values
.try_reserve(1)
.map_err(|_| resource("boolean run values"))?;
run_lengths
.try_reserve(1)
.map_err(|_| resource("boolean run lengths"))?;
run_values.push(*value);
run_lengths.push(1);
}
let count = u32::try_from(run_values.len()).map_err(|_| resource("a boolean run count"))?;
let mut output = Vec::new();
output
.try_reserve(4)
.map_err(|_| resource("a boolean RLE stream"))?;
output.extend_from_slice(&count.to_le_bytes());
if run_values.is_empty() {
return Ok(output);
}
push_bytes(&mut output, &pack_booleans(&run_values))?;
let width = bit_width(run_lengths.iter().copied().max().unwrap_or(0));
push_bytes(
&mut output,
&pack_from(width, run_lengths.len(), run_lengths.iter().copied())?,
)?;
Ok(output)
}
fn packed_length(count: usize, width: u8) -> u64 {
1 + u64::try_from(count.saturating_mul(usize::from(width)).div_ceil(8)).unwrap_or(u64::MAX)
}
fn bit_width(value: u64) -> u8 {
(u64::BITS - value.leading_zeros()) as u8
}
fn bit_width_of(value: i128) -> u8 {
u64::try_from(value).map(bit_width).unwrap_or(64)
}
fn resource(what: &str) -> Error {
Error::resource_limit(format!("unable to allocate {what}"), None)
}
fn invalid(what: &str) -> Error {
Error::invalid_argument(what)
}
#[cfg(test)]
mod tests {
use super::{
DISTINCT_VALUE_LIMIT, PreparedValues, ValueEncoding, boolean_rle, candidates, encode,
estimate, pack_from, prepare,
};
use crate::array::ScalarValue;
use crate::codec::transform;
use crate::schema::LogicalType;
fn prepared(
logical_type: &LogicalType,
values: Vec<ScalarValue<'_>>,
) -> (PreparedValues, super::ValueProfile) {
prepare(logical_type, values.into_iter().map(Ok)).expect("well-formed dense values")
}
#[test]
fn packed_values_are_lsb_first() {
assert_eq!(
pack_from(3, 2, [2_u64, 4].into_iter()).expect("packs"),
vec![3, 0x22]
);
}
#[test]
fn a_zero_width_packed_stream_is_only_its_width_byte() {
assert_eq!(
pack_from(0, 5, [0_u64; 5].into_iter()).expect("packs"),
vec![0]
);
}
#[test]
fn boolean_rle_has_the_reader_order() {
assert_eq!(
boolean_rle(&[true, true, false]).expect("encodes"),
vec![2, 0, 0, 0, 1, 2, 6]
);
}
#[test]
fn an_empty_boolean_rle_stream_is_only_its_run_count() {
assert_eq!(boolean_rle(&[]).expect("encodes"), vec![0, 0, 0, 0]);
assert_eq!(
transform::booleans(&boolean_rle(&[]).expect("encodes"), 0, 6, "empty")
.expect("the reader accepts an empty boolean RLE stream"),
Vec::<bool>::new()
);
}
#[test]
fn writer_candidates_use_reader_transform_contracts() {
let (integers, profile) = prepared(
&LogicalType::Int64,
vec![
ScalarValue::Int64(-10),
ScalarValue::Int64(-8),
ScalarValue::Int64(-6),
ScalarValue::Int64(-4),
],
);
for encoding in [
ValueEncoding::Raw,
ValueEncoding::BitPacked,
ValueEncoding::FrameOfReference,
ValueEncoding::Delta,
ValueEncoding::DeltaOfDelta,
] {
if let Ok(candidate) = encode(&LogicalType::Int64, &integers, &profile, encoding) {
let stream = &candidate.streams[0];
let decoded: Vec<i64> = transform::integer(
&stream.bytes,
stream.element_count,
8,
true,
stream.transform,
"writer candidate",
)
.expect("the reader accepts the writer's stream");
assert_eq!(decoded, vec![-10_i64, -8, -6, -4]);
}
}
let (floats, profile) = prepared(
&LogicalType::Float64,
vec![
ScalarValue::Float64(f64::from_bits(0x8000_0000_0000_0000)),
ScalarValue::Float64(f64::from_bits(0x7ff8_0000_0000_0042)),
ScalarValue::Float64(1.25),
],
);
let candidate = encode(
&LogicalType::Float64,
&floats,
&profile,
ValueEncoding::ByteStreamSplit,
)
.expect("floats split");
let stream = &candidate.streams[0];
let restored = transform::byte_stream_split(
&stream.bytes,
stream.element_count,
8,
"writer byte-stream split",
)
.expect("the reader restores the split");
let expected = [
0x8000_0000_0000_0000_u64,
0x7ff8_0000_0000_0042,
1.25_f64.to_bits(),
]
.into_iter()
.flat_map(u64::to_le_bytes)
.collect::<Vec<_>>();
assert_eq!(restored, expected);
let (booleans, profile) = prepared(
&LogicalType::Bool,
vec![
ScalarValue::Bool(true),
ScalarValue::Bool(true),
ScalarValue::Bool(false),
ScalarValue::Bool(false),
],
);
let candidate = encode(
&LogicalType::Bool,
&booleans,
&profile,
ValueEncoding::BooleanRle,
)
.expect("booleans run-length encode");
let stream = &candidate.streams[0];
assert_eq!(
transform::booleans(
&stream.bytes,
stream.element_count,
stream.transform,
"writer boolean RLE",
)
.expect("the reader accepts the writer's runs"),
vec![true, true, false, false]
);
}
#[test]
fn every_estimate_matches_the_stream_it_prices() {
let cases: Vec<(LogicalType, Vec<ScalarValue<'_>>)> = vec![
(
LogicalType::Int64,
(0..9).map(|row| ScalarValue::Int64(row * 3)).collect(),
),
(
LogicalType::Int64,
(0..9).map(|_| ScalarValue::Int64(-7)).collect(),
),
(
LogicalType::UInt32,
(0..9).map(|row| ScalarValue::UInt32(row % 4)).collect(),
),
(
LogicalType::Int64,
vec![ScalarValue::Int64(i64::MIN), ScalarValue::Int64(i64::MAX)],
),
(
LogicalType::Bool,
(0..9).map(|row| ScalarValue::Bool(row % 3 == 0)).collect(),
),
(
LogicalType::Bool,
(0..9).map(|_| ScalarValue::Bool(false)).collect(),
),
(
LogicalType::Float64,
(0..9).map(|row| ScalarValue::Float64(row as f64)).collect(),
),
(
LogicalType::Utf8,
(0..9).map(|_| ScalarValue::Utf8("abc")).collect(),
),
(
LogicalType::Utf8,
(0..9).map(|_| ScalarValue::Utf8("")).collect(),
),
(
LogicalType::Binary,
(0..9).map(|_| ScalarValue::Binary(b"xy")).collect(),
),
];
for (logical_type, values) in cases {
let (prepared, profile) = prepared(&logical_type, values);
for candidate in candidates(&logical_type, &prepared, &profile) {
let estimated = estimate(&logical_type, &prepared, &profile, candidate)
.expect("a listed candidate is priced");
let encoded = encode(&logical_type, &prepared, &profile, candidate)
.expect("a listed candidate encodes");
let actual: Vec<u64> = encoded
.streams
.iter()
.map(|stream| stream.bytes.len() as u64)
.collect();
if candidate == ValueEncoding::BitPacked
&& matches!(prepared, PreparedValues::Bool(_))
{
assert!(actual[0] <= estimated[0], "{logical_type:?} {candidate:?}");
continue;
}
assert_eq!(estimated, actual, "{logical_type:?} {candidate:?}");
}
}
}
#[test]
fn a_stream_past_the_cardinality_cap_offers_no_dictionary() {
let values: Vec<ScalarValue<'_>> = (0..=DISTINCT_VALUE_LIMIT as i64)
.map(ScalarValue::Int64)
.collect();
let (prepared, profile) = prepared(&LogicalType::Int64, values);
assert!(profile.dictionary.is_none());
assert!(
!candidates(&LogicalType::Int64, &prepared, &profile)
.contains(&ValueEncoding::Dictionary)
);
}
#[test]
fn overflowing_differences_disqualify_only_the_transforms_they_reach() {
let (prepared, profile) = prepared(
&LogicalType::Int64,
vec![
ScalarValue::Int64(i64::MIN),
ScalarValue::Int64(i64::MAX),
ScalarValue::Int64(i64::MIN),
],
);
let offered = candidates(&LogicalType::Int64, &prepared, &profile);
assert!(!offered.contains(&ValueEncoding::Delta));
assert!(!offered.contains(&ValueEncoding::DeltaOfDelta));
assert!(offered.contains(&ValueEncoding::FrameOfReference));
}
#[test]
fn one_overflowing_difference_disqualifies_the_whole_stream() {
let (prepared, profile) = prepared(
&LogicalType::Int64,
vec![
ScalarValue::Int64(i64::MIN),
ScalarValue::Int64(i64::MAX),
ScalarValue::Int64(i64::MAX - 1),
ScalarValue::Int64(i64::MAX - 2),
],
);
assert!(
profile
.numeric
.as_ref()
.expect("numeric")
.delta_bits
.is_none()
);
assert!(
!candidates(&LogicalType::Int64, &prepared, &profile).contains(&ValueEncoding::Delta)
);
}
#[test]
fn an_encoding_that_does_not_describe_its_values_is_refused_rather_than_panicking() {
let (prepared, profile) = prepared(&LogicalType::Utf8, vec![ScalarValue::Utf8("a")]);
assert!(
encode(
&LogicalType::Utf8,
&prepared,
&profile,
ValueEncoding::Delta
)
.is_err()
);
assert!(
encode(
&LogicalType::Utf8,
&prepared,
&profile,
ValueEncoding::BooleanRle
)
.is_err()
);
}
}