1use std::fmt;
12
13use crate::types::LogicalType;
14
15#[derive(Debug, Clone, PartialEq)]
22#[non_exhaustive]
23pub enum Value {
24 Null,
26 Boolean(bool),
28 TinyInt(i8),
30 SmallInt(i16),
32 Integer(i32),
34 BigInt(i64),
36 HugeInt(i128),
38 UTinyInt(u8),
40 USmallInt(u16),
42 UInteger(u32),
44 UBigInt(u64),
46 UHugeInt(u128),
48 Float(f32),
50 Double(f64),
52 Decimal {
54 unscaled: i128,
56 width: u8,
58 scale: u8,
60 },
61 Varchar(String),
63 Blob(Vec<u8>),
65 Date(i32),
67 Time(i64),
69 TimeTz(i64),
75 Timestamp(i64),
77 TimestampTz(i64),
84 Interval {
90 months: i32,
92 days: i32,
94 micros: i64,
96 },
97 List {
99 element: LogicalType,
101 values: Vec<Value>,
103 },
104 Struct(Vec<(String, Value)>),
106 Map {
124 key: Box<LogicalType>,
126 value: Box<LogicalType>,
128 entries: Vec<(Value, Value)>,
130 },
131}
132
133impl Value {
134 #[must_use]
140 pub fn map(key: LogicalType, value: LogicalType, entries: Vec<(Self, Self)>) -> Self {
141 Self::Map { key: Box::new(key), value: Box::new(value), entries }
142 }
143
144 #[must_use]
155 pub fn footprint(&self) -> usize {
156 size_of::<Self>() + self.heap()
157 }
158
159 fn heap(&self) -> usize {
161 match self {
162 Self::Varchar(text) => text.capacity(),
163 Self::Blob(bytes) => bytes.capacity(),
164 Self::List { values, .. } => {
165 values.capacity() * size_of::<Self>() + values.iter().map(Self::heap).sum::<usize>()
166 }
167 Self::Struct(fields) => {
168 fields.capacity() * size_of::<(String, Self)>()
169 + fields
170 .iter()
171 .map(|(name, value)| name.capacity() + value.heap())
172 .sum::<usize>()
173 }
174 Self::Map { entries, .. } => {
178 2 * size_of::<LogicalType>()
179 + entries.capacity() * size_of::<(Self, Self)>()
180 + entries.iter().map(|(key, value)| key.heap() + value.heap()).sum::<usize>()
181 }
182 _ => 0,
183 }
184 }
185
186 #[must_use]
188 pub fn is_null(&self) -> bool {
189 matches!(self, Self::Null)
190 }
191
192 #[must_use]
194 pub fn logical_type(&self) -> LogicalType {
195 match self {
196 Self::Null => LogicalType::Null,
197 Self::Boolean(_) => LogicalType::Boolean,
198 Self::TinyInt(_) => LogicalType::TinyInt,
199 Self::SmallInt(_) => LogicalType::SmallInt,
200 Self::Integer(_) => LogicalType::Integer,
201 Self::BigInt(_) => LogicalType::BigInt,
202 Self::HugeInt(_) => LogicalType::HugeInt,
203 Self::UTinyInt(_) => LogicalType::UTinyInt,
204 Self::USmallInt(_) => LogicalType::USmallInt,
205 Self::UInteger(_) => LogicalType::UInteger,
206 Self::UBigInt(_) => LogicalType::UBigInt,
207 Self::UHugeInt(_) => LogicalType::UHugeInt,
208 Self::Float(_) => LogicalType::Float,
209 Self::Double(_) => LogicalType::Double,
210 Self::Decimal { width, scale, .. } => {
211 LogicalType::Decimal { width: *width, scale: *scale }
212 }
213 Self::Varchar(_) => LogicalType::Varchar,
214 Self::Blob(_) => LogicalType::Blob,
215 Self::Date(_) => LogicalType::Date,
216 Self::Time(_) => LogicalType::Time,
217 Self::TimeTz(_) => LogicalType::TimeTz,
218 Self::Timestamp(_) => LogicalType::Timestamp,
219 Self::TimestampTz(_) => LogicalType::TimestampTz,
220 Self::Interval { .. } => LogicalType::Interval,
221 Self::List { element, .. } => LogicalType::list(element.clone()),
222 Self::Struct(fields) => LogicalType::Struct(
223 fields
224 .iter()
225 .map(|(name, value)| crate::types::Field::new(name, value.logical_type()))
226 .collect(),
227 ),
228 Self::Map { key, value, .. } => LogicalType::Map(key.clone(), value.clone()),
229 }
230 }
231
232 #[must_use]
238 pub fn as_i64(&self) -> Option<i64> {
239 match *self {
240 Self::TinyInt(v) => Some(i64::from(v)),
241 Self::SmallInt(v) => Some(i64::from(v)),
242 Self::Integer(v) => Some(i64::from(v)),
243 Self::BigInt(v) => Some(v),
244 Self::UTinyInt(v) => Some(i64::from(v)),
245 Self::USmallInt(v) => Some(i64::from(v)),
246 Self::UInteger(v) => Some(i64::from(v)),
247 Self::UBigInt(v) => i64::try_from(v).ok(),
248 Self::HugeInt(v) => i64::try_from(v).ok(),
249 Self::UHugeInt(v) => i64::try_from(v).ok(),
250 _ => None,
251 }
252 }
253
254 #[must_use]
256 pub fn as_bool(&self) -> Option<bool> {
257 match *self {
258 Self::Boolean(v) => Some(v),
259 _ => None,
260 }
261 }
262
263 #[must_use]
265 pub fn as_str(&self) -> Option<&str> {
266 match self {
267 Self::Varchar(v) => Some(v),
268 _ => None,
269 }
270 }
271}
272
273impl fmt::Display for Value {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 match self {
276 Self::Null => f.write_str("NULL"),
277 Self::Boolean(v) => f.write_str(if *v { "true" } else { "false" }),
278 Self::TinyInt(v) => write!(f, "{v}"),
279 Self::SmallInt(v) => write!(f, "{v}"),
280 Self::Integer(v) => write!(f, "{v}"),
281 Self::BigInt(v) => write!(f, "{v}"),
282 Self::HugeInt(v) => write!(f, "{v}"),
283 Self::UTinyInt(v) => write!(f, "{v}"),
284 Self::USmallInt(v) => write!(f, "{v}"),
285 Self::UInteger(v) => write!(f, "{v}"),
286 Self::UBigInt(v) => write!(f, "{v}"),
287 Self::UHugeInt(v) => write!(f, "{v}"),
288 Self::Float(v) => write_float(f, *v),
289 Self::Double(v) => write_float(f, *v),
290 Self::Decimal { unscaled, scale, .. } => write_decimal(f, *unscaled, *scale),
291 Self::Varchar(v) => f.write_str(v),
292 Self::Blob(v) => write_blob(f, v),
293 Self::Date(v) => write_date(f, *v),
294 Self::Time(v) => write_time(f, *v),
295 Self::TimeTz(v) => {
299 write_time(f, *v)?;
300 f.write_str(UTC)
301 }
302 Self::Timestamp(v) => write_timestamp(f, *v),
303 Self::TimestampTz(v) => {
304 write_timestamp(f, *v)?;
305 f.write_str(UTC)
306 }
307 Self::Interval { months, days, micros } => write_interval(f, *months, *days, *micros),
308 Self::List { values, .. } => {
309 f.write_str("[")?;
310 for (index, value) in values.iter().enumerate() {
311 if index > 0 {
312 f.write_str(", ")?;
313 }
314 write!(f, "{value}")?;
315 }
316 f.write_str("]")
317 }
318 Self::Struct(fields) => {
319 f.write_str("{")?;
320 for (index, (name, value)) in fields.iter().enumerate() {
321 if index > 0 {
322 f.write_str(", ")?;
323 }
324 write!(f, "'{name}': {value}")?;
325 }
326 f.write_str("}")
327 }
328 Self::Map { entries, .. } => {
332 f.write_str("{")?;
333 for (index, (key, value)) in entries.iter().enumerate() {
334 if index > 0 {
335 f.write_str(", ")?;
336 }
337 write!(f, "{key}={value}")?;
338 }
339 f.write_str("}")
340 }
341 }
342 }
343}
344
345impl Value {
346 #[must_use]
348 pub fn to_string_at_offset(&self, offset_seconds: i32) -> String {
349 match self {
352 Self::TimestampTz(micros) => {
353 let local = micros.saturating_add(i64::from(offset_seconds) * 1_000_000);
354 format!("{}{}", Self::Timestamp(local), offset_text(offset_seconds))
355 }
356 Self::TimeTz(micros) => {
357 format!("{}{}", Self::Time(*micros), offset_text(offset_seconds))
358 }
359 other => other.to_string(),
360 }
361 }
362}
363
364fn offset_text(seconds: i32) -> String {
365 let sign = if seconds < 0 { '-' } else { '+' };
366 let absolute = seconds.unsigned_abs();
367 let hours = absolute / 3600;
368 let minutes = (absolute / 60) % 60;
369 let remainder = absolute % 60;
370 if remainder != 0 {
371 format!("{sign}{hours:02}:{minutes:02}:{remainder:02}")
372 } else if minutes != 0 {
373 format!("{sign}{hours:02}:{minutes:02}")
374 } else {
375 format!("{sign}{hours:02}")
376 }
377}
378
379trait Real: Copy + fmt::Display + fmt::LowerExp {
386 fn is_nan(self) -> bool;
387 fn is_infinite(self) -> bool;
388 fn is_sign_negative(self) -> bool;
389}
390
391impl Real for f32 {
392 fn is_nan(self) -> bool {
393 Self::is_nan(self)
394 }
395
396 fn is_infinite(self) -> bool {
397 Self::is_infinite(self)
398 }
399
400 fn is_sign_negative(self) -> bool {
401 Self::is_sign_negative(self)
402 }
403}
404
405impl Real for f64 {
406 fn is_nan(self) -> bool {
407 Self::is_nan(self)
408 }
409
410 fn is_infinite(self) -> bool {
411 Self::is_infinite(self)
412 }
413
414 fn is_sign_negative(self) -> bool {
415 Self::is_sign_negative(self)
416 }
417}
418
419fn write_float<T: Real>(f: &mut fmt::Formatter<'_>, value: T) -> fmt::Result {
429 if value.is_nan() {
434 return f.write_str(if value.is_sign_negative() { "-nan" } else { "nan" });
435 }
436 if value.is_infinite() {
437 return f.write_str(if value.is_sign_negative() { "-inf" } else { "inf" });
438 }
439 let scientific = format!("{value:e}");
440 let (mantissa, exponent) = scientific.split_once('e').unwrap_or((scientific.as_str(), "0"));
441 let exponent: i32 = exponent.parse().unwrap_or(0);
442 if (-4..16).contains(&exponent) {
443 let text = format!("{value}");
444 if text.contains('.') {
445 return f.write_str(&text);
446 }
447 return write!(f, "{text}.0");
448 }
449 let sign = if exponent < 0 { '-' } else { '+' };
450 write!(f, "{mantissa}e{sign}{:02}", exponent.abs())
451}
452
453fn write_decimal(f: &mut fmt::Formatter<'_>, unscaled: i128, scale: u8) -> fmt::Result {
454 if scale == 0 {
455 return write!(f, "{unscaled}");
456 }
457 let negative = unscaled < 0;
458 let digits = unscaled.unsigned_abs().to_string();
460 let scale = usize::from(scale);
461 let (whole, fraction) = if digits.len() > scale {
462 let split = digits.len() - scale;
463 (digits[..split].to_string(), digits[split..].to_string())
464 } else {
465 ("0".to_string(), format!("{:0>scale$}", digits))
466 };
467 if negative {
468 f.write_str("-")?;
469 }
470 write!(f, "{whole}.{fraction}")
471}
472
473fn write_blob(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
480 for &byte in bytes {
481 if (byte.is_ascii_graphic() || byte == b' ') && !matches!(byte, b'\\' | b'\'' | b'"') {
482 write!(f, "{}", byte as char)?;
483 } else {
484 write!(f, "\\x{byte:02X}")?;
485 }
486 }
487 Ok(())
488}
489
490#[must_use]
497pub fn civil_from_days(days: i32) -> (i32, u32, u32) {
498 let z = i64::from(days) + 719_468;
499 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
500 let day_of_era = z - era * 146_097;
501 let year_of_era =
502 (day_of_era - day_of_era / 1460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
503 let year = year_of_era + era * 400;
504 let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
505 let shifted_month = (5 * day_of_year + 2) / 153;
506 let day = day_of_year - (153 * shifted_month + 2) / 5 + 1;
507 let month = if shifted_month < 10 { shifted_month + 3 } else { shifted_month - 9 };
508 let year = if month <= 2 { year + 1 } else { year };
509 #[expect(clippy::cast_possible_truncation, reason = "the ranges are 1 to 12 and 1 to 31")]
510 (year as i32, month as u32, day as u32)
511}
512
513#[must_use]
528pub fn interval_micros(months: i32, days: i32, micros: i64) -> i128 {
529 const MICROS_PER_DAY: i128 = 86_400 * 1_000_000;
530 const DAYS_PER_MONTH: i128 = 30;
531 (i128::from(months) * DAYS_PER_MONTH + i128::from(days)) * MICROS_PER_DAY + i128::from(micros)
532}
533
534#[must_use]
536pub fn days_from_civil(year: i32, month: u32, day: u32) -> i32 {
537 let year = i64::from(year) - i64::from(month <= 2);
538 let era = if year >= 0 { year } else { year - 399 } / 400;
539 let year_of_era = year - era * 400;
540 let month = i64::from(month);
541 let shifted_month = if month > 2 { month - 3 } else { month + 9 };
542 let day_of_year = (153 * shifted_month + 2) / 5 + i64::from(day) - 1;
543 let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
544 #[expect(clippy::cast_possible_truncation, reason = "a date in i32 range stays in i32 range")]
545 ((era * 146_097 + day_of_era - 719_468) as i32)
546}
547
548fn write_date(f: &mut fmt::Formatter<'_>, days: i32) -> fmt::Result {
555 let (year, month, day) = civil_from_days(days);
556 if year <= 0 {
557 write!(f, "{:04}-{month:02}-{day:02} (BC)", 1 - year)
558 } else {
559 write!(f, "{year:04}-{month:02}-{day:02}")
560 }
561}
562
563const UTC: &str = "+00";
569
570fn write_time(f: &mut fmt::Formatter<'_>, micros: i64) -> fmt::Result {
571 let seconds = micros.div_euclid(1_000_000);
572 let fraction = micros.rem_euclid(1_000_000);
573 let (hours, minutes, seconds) = (seconds / 3600, (seconds / 60) % 60, seconds % 60);
574 write!(f, "{hours:02}:{minutes:02}:{seconds:02}")?;
575 if fraction != 0 {
576 let text = format!("{fraction:06}");
578 write!(f, ".{}", text.trim_end_matches('0'))?;
579 }
580 Ok(())
581}
582
583fn write_timestamp(f: &mut fmt::Formatter<'_>, micros: i64) -> fmt::Result {
584 const MICROS_PER_DAY: i64 = 86_400 * 1_000_000;
585 let days = micros.div_euclid(MICROS_PER_DAY);
586 let within_day = micros.rem_euclid(MICROS_PER_DAY);
587 let Ok(days) = i32::try_from(days) else {
588 return f.write_str("timestamp out of range");
589 };
590 write_date(f, days)?;
591 f.write_str(" ")?;
592 write_time(f, within_day)
593}
594
595fn write_interval(f: &mut fmt::Formatter<'_>, months: i32, days: i32, micros: i64) -> fmt::Result {
596 let mut wrote = false;
597 let space = |f: &mut fmt::Formatter<'_>, wrote: &mut bool| -> fmt::Result {
598 if *wrote {
599 f.write_str(" ")?;
600 }
601 *wrote = true;
602 Ok(())
603 };
604 let (years, rest_months) = (months / 12, months % 12);
605 if years != 0 {
606 space(f, &mut wrote)?;
607 write!(f, "{years} year{}", plural(years))?;
608 }
609 if rest_months != 0 {
610 space(f, &mut wrote)?;
611 write!(f, "{rest_months} month{}", plural(rest_months))?;
612 }
613 if days != 0 {
614 space(f, &mut wrote)?;
615 write!(f, "{days} day{}", plural(days))?;
616 }
617 if micros != 0 || !wrote {
618 space(f, &mut wrote)?;
619 if micros < 0 {
620 f.write_str("-")?;
621 }
622 write_time(f, micros.abs())?;
623 }
624 Ok(())
625}
626
627fn plural(n: i32) -> &'static str {
628 if n == 1 || n == -1 { "" } else { "s" }
629}
630
631#[cfg(test)]
632mod tests {
633 use super::{Value, civil_from_days, days_from_civil};
634 use crate::types::LogicalType;
635
636 #[test]
637 fn a_value_knows_its_own_type() {
638 assert_eq!(Value::Integer(1).logical_type(), LogicalType::Integer);
639 assert_eq!(Value::Null.logical_type(), LogicalType::Null);
640 let list = Value::List { element: LogicalType::Varchar, values: Vec::new() };
641 assert_eq!(list.logical_type(), LogicalType::list(LogicalType::Varchar));
644 }
645
646 #[test]
647 fn the_date_conversion_is_its_own_inverse() {
648 for days in days_from_civil(1600, 1, 1)..days_from_civil(2400, 1, 1) {
651 let (year, month, day) = civil_from_days(days);
652 assert_eq!(days_from_civil(year, month, day), days, "{year}-{month}-{day}");
653 }
654 }
655
656 #[test]
657 fn the_epoch_is_where_it_should_be() {
658 assert_eq!(days_from_civil(1970, 1, 1), 0);
659 assert_eq!(civil_from_days(0), (1970, 1, 1));
660 assert_eq!(Value::Date(0).to_string(), "1970-01-01");
661 assert_eq!(Value::Date(19_723).to_string(), "2024-01-01");
662 assert_eq!(Value::Date(19_737).to_string(), "2024-01-15");
663 }
664
665 #[test]
666 fn a_leap_day_is_a_day() {
667 assert_eq!(civil_from_days(days_from_civil(2024, 2, 29)), (2024, 2, 29));
668 assert_eq!(days_from_civil(1900, 3, 1) - days_from_civil(1900, 2, 28), 1);
671 assert_eq!(days_from_civil(2000, 3, 1) - days_from_civil(2000, 2, 28), 2);
672 }
673
674 #[test]
675 fn a_year_at_or_before_zero_prints_in_the_era_before_christ() {
676 let date = |year, month, day| Value::Date(days_from_civil(year, month, day)).to_string();
677 assert_eq!(date(1, 1, 1), "0001-01-01");
680 assert_eq!(date(0, 1, 1), "0001-01-01 (BC)");
681 assert_eq!(date(0, 12, 31), "0001-12-31 (BC)");
682 assert_eq!(date(-1, 1, 1), "0002-01-01 (BC)");
683 assert_eq!(date(-2020, 3, 4), "2021-03-04 (BC)");
684 let timestamp = |year, month, day| {
685 Value::Timestamp(i64::from(days_from_civil(year, month, day)) * 86_400 * 1_000_000)
686 .to_string()
687 };
688 assert_eq!(timestamp(0, 1, 1), "0001-01-01 (BC) 00:00:00");
689 }
690
691 #[test]
692 fn times_print_with_the_trailing_zeros_trimmed() {
693 assert_eq!(Value::Time(0).to_string(), "00:00:00");
694 assert_eq!(Value::Time(3_723_000_000).to_string(), "01:02:03");
695 assert_eq!(Value::Time(3_723_500_000).to_string(), "01:02:03.5");
696 assert_eq!(Value::Time(3_723_000_001).to_string(), "01:02:03.000001");
697 }
698
699 #[test]
700 fn a_timestamp_before_the_epoch_borrows_from_the_day() {
701 assert_eq!(Value::Timestamp(-1).to_string(), "1969-12-31 23:59:59.999999");
704 assert_eq!(Value::Timestamp(0).to_string(), "1970-01-01 00:00:00");
705 }
706
707 #[test]
708 fn a_decimal_prints_at_its_scale() {
709 let d = |unscaled, scale| Value::Decimal { unscaled, width: 18, scale }.to_string();
710 assert_eq!(d(1234, 2), "12.34");
711 assert_eq!(d(-1234, 2), "-12.34");
712 assert_eq!(d(5, 3), "0.005");
713 assert_eq!(d(-5, 3), "-0.005");
714 assert_eq!(d(1234, 0), "1234");
715 assert_eq!(d(1_000_000, 6), "1.000000");
716 }
717
718 #[test]
719 fn a_float_keeps_the_point_that_says_it_is_one() {
720 assert_eq!(Value::Double(1.0).to_string(), "1.0");
721 assert_eq!(Value::Double(-3.0).to_string(), "-3.0");
722 assert_eq!(Value::Double(1.5).to_string(), "1.5");
723 assert_eq!(Value::Double(-0.0).to_string(), "-0.0");
724 assert_eq!(Value::Float(0.5).to_string(), "0.5");
725 }
726
727 #[test]
728 fn a_float_is_printed_from_its_own_width_rather_than_widened_first() {
729 assert_eq!(Value::Float(0.1).to_string(), "0.1");
732 assert_eq!(Value::Float(1.0).to_string(), "1.0");
733 }
734
735 #[test]
736 fn a_float_switches_to_an_exponent_where_duckdb_switches() {
737 assert_eq!(Value::Double(1e15).to_string(), "1000000000000000.0");
738 assert_eq!(Value::Double(1e16).to_string(), "1e+16");
739 assert_eq!(Value::Double(1e20).to_string(), "1e+20");
740 assert_eq!(Value::Double(1e-4).to_string(), "0.0001");
741 assert_eq!(Value::Double(1e-5).to_string(), "1e-05");
742 assert_eq!(Value::Double(1.234_567_890_123_456_8e17).to_string(), "1.2345678901234568e+17");
743 }
744
745 #[test]
746 fn a_float_that_is_not_a_number_says_so_the_way_duckdb_says_it() {
747 assert_eq!(Value::Double(f64::INFINITY).to_string(), "inf");
748 assert_eq!(Value::Double(f64::NEG_INFINITY).to_string(), "-inf");
749 assert_eq!(Value::Double(f64::NAN).to_string(), "nan");
750 assert_eq!(Value::Double(-f64::NAN).to_string(), "-nan");
755 assert_eq!(Value::Float(-f32::NAN).to_string(), "-nan");
756 }
757
758 #[test]
759 fn an_interval_keeps_months_days_and_micros_apart() {
760 let i = |months, days, micros| Value::Interval { months, days, micros }.to_string();
761 assert_eq!(i(14, 3, 3_723_000_000), "1 year 2 months 3 days 01:02:03");
762 assert_eq!(i(1, 0, 0), "1 month");
763 assert_eq!(i(0, 0, 0), "00:00:00");
764 assert_eq!(i(0, 0, -1_000_000), "-00:00:01");
765 }
766
767 #[test]
768 fn a_blob_escapes_what_is_not_printable() {
769 assert_eq!(Value::Blob(b"ok".to_vec()).to_string(), "ok");
770 assert_eq!(Value::Blob(vec![0, 1, b'a']).to_string(), "\\x00\\x01a");
771 assert_eq!(Value::Blob(vec![0x7f, 0xff]).to_string(), "\\x7F\\xFF");
772 assert_eq!(Value::Blob(br#"'"\"#.to_vec()).to_string(), "\\x27\\x22\\x5C");
774 assert_eq!(Value::Blob(b" &`~".to_vec()).to_string(), " &`~");
775 }
776
777 #[test]
778 fn a_limit_that_does_not_fit_is_none_rather_than_clamped() {
779 assert_eq!(Value::Integer(5).as_i64(), Some(5));
780 assert_eq!(Value::UBigInt(u64::MAX).as_i64(), None);
781 assert_eq!(Value::Varchar("5".into()).as_i64(), None);
782 }
783
784 #[test]
785 fn a_footprint_is_the_value_plus_what_it_owns() {
786 let bare = Value::Integer(1).footprint();
787 assert_eq!(bare, size_of::<Value>(), "a number owns nothing");
788 assert_eq!(
789 Value::Boolean(true).footprint(),
790 bare,
791 "the enum is one width whatever is in it"
792 );
793 let text = "a string long enough to be on the heap in any implementation".to_string();
794 assert_eq!(Value::Varchar(text.clone()).footprint(), bare + text.capacity());
795 let list = Value::List {
796 element: LogicalType::Varchar,
797 values: vec![Value::Varchar(text.clone())],
798 };
799 assert_eq!(list.footprint(), bare + size_of::<Value>() + text.capacity());
803 }
804
805 #[test]
809 fn a_value_is_sixty_four_bytes_and_a_map_did_not_widen_it() {
810 assert_eq!(size_of::<Value>(), 64);
811 let entries = vec![(Value::Varchar("a".to_string()), Value::Varchar("b".to_string()))];
812 let map = Value::map(LogicalType::Varchar, LogicalType::Varchar, entries);
813 assert_eq!(
816 map.footprint(),
817 size_of::<Value>() + 2 * size_of::<LogicalType>() + 2 * size_of::<Value>() + 2
818 );
819 assert_eq!(
820 map.logical_type(),
821 LogicalType::map(LogicalType::Varchar, LogicalType::Varchar)
822 );
823 }
824}