Skip to main content

ferrule_sql/
value.rs

1use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5/// A type hint used by formatters to choose presentation.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7pub enum TypeHint {
8    Null,
9    Bool,
10    Int64,
11    Float64,
12    Decimal,
13    String,
14    Bytes,
15    Date,
16    Time,
17    DateTime,
18    DateTimeTz,
19    Json,
20    Uuid,
21    Array,
22    Other,
23}
24
25/// Metadata about a single column.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ColumnInfo {
28    pub name: String,
29    pub type_hint: TypeHint,
30    pub nullable: bool,
31}
32
33/// A unified value type across all backends.
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35#[serde(untagged)]
36pub enum Value {
37    Null,
38    Bool(bool),
39    Int64(i64),
40    Float64(f64),
41    // Decimal stored as string to avoid needing rust_decimal in public API for now.
42    Decimal(String),
43    String(String),
44    Bytes(Vec<u8>),
45    Date(NaiveDate),
46    Time(NaiveTime),
47    DateTime(NaiveDateTime),
48    DateTimeTz(DateTime<Utc>),
49    Json(serde_json::Value),
50    Uuid(String),
51    Array(Vec<Value>),
52}
53
54impl fmt::Display for Value {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            Value::Null => write!(f, "NULL"),
58            Value::Bool(v) => write!(f, "{v}"),
59            Value::Int64(v) => write!(f, "{v}"),
60            Value::Float64(v) => write!(f, "{v}"),
61            Value::Decimal(v) => write!(f, "{v}"),
62            Value::String(v) => write!(f, "{v}"),
63            Value::Bytes(v) => write!(f, "<{} bytes>", v.len()),
64            Value::Date(v) => write!(f, "{v}"),
65            Value::Time(v) => write!(f, "{v}"),
66            Value::DateTime(v) => write!(f, "{v}"),
67            Value::DateTimeTz(v) => write!(f, "{v}"),
68            Value::Json(v) => write!(f, "{v}"),
69            Value::Uuid(v) => write!(f, "{v}"),
70            Value::Array(v) => {
71                let items: Vec<String> = v.iter().map(|i| i.to_string()).collect();
72                write!(f, "[{}]", items.join(", "))
73            }
74        }
75    }
76}
77
78impl Value {
79    /// Approximate in-memory byte footprint of this value's *payload*,
80    /// used by the `ferrule-sql` size guards (`max_cell_bytes` /
81    /// `max_row_bytes` / `max_total_buffered_bytes`) to fail fast on a
82    /// pathological cell before it is retained.
83    ///
84    /// This counts the bytes the payload actually owns on the heap (the
85    /// `String` / `Bytes` / `Json` / nested-`Array` contents), not the
86    /// fixed 24-ish-byte `Value` enum discriminant+inline scalars. The
87    /// goal is a cheap, monotonic proxy for "how much memory does
88    /// keeping this cell cost" — exact accounting is unnecessary because
89    /// the guards are coarse ceilings, not an RSS budget. `Json` is
90    /// measured by its serialized length (a tight upper bound on the
91    /// retained `serde_json::Value` tree); `Array` recurses so a deeply
92    /// nested array is charged for its whole subtree.
93    #[must_use]
94    pub fn byte_size(&self) -> usize {
95        match self {
96            Value::Null
97            | Value::Bool(_)
98            | Value::Int64(_)
99            | Value::Float64(_)
100            | Value::Date(_)
101            | Value::Time(_)
102            | Value::DateTime(_)
103            | Value::DateTimeTz(_) => std::mem::size_of::<Value>(),
104            Value::Decimal(s) | Value::String(s) | Value::Uuid(s) => s.len(),
105            Value::Bytes(b) => b.len(),
106            Value::Json(j) => json_byte_size(j),
107            Value::Array(items) => items.iter().map(Value::byte_size).sum(),
108        }
109    }
110}
111
112/// Serialized-length proxy for a `serde_json::Value`'s retained size.
113///
114/// `serde_json::to_string(...).len()` would allocate; this walks the
115/// tree counting characters instead, so measuring a giant JSON cell for
116/// a size-guard check never doubles its memory cost.
117fn json_byte_size(v: &serde_json::Value) -> usize {
118    match v {
119        serde_json::Value::Null => 4,
120        serde_json::Value::Bool(_) => 5,
121        serde_json::Value::Number(n) => n.to_string().len(),
122        serde_json::Value::String(s) => s.len() + 2,
123        serde_json::Value::Array(a) => {
124            2 + a.iter().map(json_byte_size).sum::<usize>() + a.len().saturating_sub(1)
125        }
126        serde_json::Value::Object(o) => {
127            2 + o
128                .iter()
129                .map(|(k, val)| k.len() + 3 + json_byte_size(val))
130                .sum::<usize>()
131                + o.len().saturating_sub(1)
132        }
133    }
134}
135
136/// A row is just a vector of values, indexed by column position.
137pub type Row = Vec<Value>;