Skip to main content

sqlx_cloudflare_core/
value.rs

1use std::fmt::{self, Display, Formatter};
2
3use sqlx_core::error::BoxDynError;
4
5/// The storage class of a single value, which is all these backends report.
6///
7/// There is no declared column type to go on: D1 and Durable Object storage
8/// hand back JavaScript values, not SQLite's column metadata. So the type is
9/// read off each value as it arrives, and a column's type is the type of
10/// whatever is in it.
11///
12/// Non-exhaustive, like [`Value`]: a new storage class must not break a
13/// `match` outside this crate.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[non_exhaustive]
16pub enum TypeInfo {
17    /// A `NULL` value, or a column with no non-`NULL` value to type it by.
18    Null,
19    /// A whole number within ±(2^53 − 1).
20    Integer,
21    /// Any other number.
22    Real,
23    /// A string.
24    Text,
25    /// Bytes.
26    Blob,
27}
28
29impl Display for TypeInfo {
30    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
31        f.pad(sqlx_core::type_info::TypeInfo::name(self))
32    }
33}
34
35impl sqlx_core::type_info::TypeInfo for TypeInfo {
36    fn is_null(&self) -> bool {
37        matches!(self, TypeInfo::Null)
38    }
39
40    fn name(&self) -> &str {
41        match self {
42            TypeInfo::Null => "NULL",
43            TypeInfo::Integer => "INTEGER",
44            TypeInfo::Real => "REAL",
45            TypeInfo::Text => "TEXT",
46            TypeInfo::Blob => "BLOB",
47        }
48    }
49}
50
51/// One SQLite value on the Rust side of the JavaScript boundary, in either
52/// direction: a parameter a query binds, or a value a result row holds.
53///
54/// One type for both, as sqlx's `Any` driver has, because both directions
55/// are the same thing here -- plain values that cross as JavaScript ones.
56/// sqlx's built-in drivers split them only because what they read is a C
57/// handle or wire bytes, decoded lazily.
58///
59/// Owned rather than borrowed: sqlx 0.9's `Arguments` has no lifetime, and the
60/// backends return a whole result set as JavaScript values, leaving nothing on
61/// the Rust side for a row to borrow from.
62///
63/// Non-exhaustive so that a backend growing a representation -- a `BigInt`,
64/// say -- is not a breaking change for `Encode` impls outside this crate.
65#[derive(Debug, Clone, PartialEq)]
66#[non_exhaustive]
67pub enum Value {
68    /// SQL `NULL`: JavaScript `null`.
69    Null,
70    /// A whole number, within ±(2^53 − 1) because it crosses as a JavaScript
71    /// number and not a `BigInt`. A result only holds integers in that range;
72    /// binding a wider one is an encode error rather than a rounded value.
73    Integer(i64),
74    /// Any other number, stored as `REAL`.
75    Real(f64),
76    /// A JavaScript string, stored as `TEXT`.
77    Text(String),
78    /// An `ArrayBuffer`, stored as `BLOB`.
79    Blob(Vec<u8>),
80}
81
82/// The largest integer a JavaScript number holds exactly: 2^53 − 1.
83/// `Number.MAX_SAFE_INTEGER`, in other words.
84pub const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1;
85
86/// `value`, if a JavaScript number can carry it without rounding.
87///
88/// Both backends take and return JavaScript numbers, never `BigInt`, so an
89/// integer outside ±(2^53 − 1) would come back as a different integer. That
90/// is an error here rather than a silently wrong row.
91///
92/// # Errors
93///
94/// When `value` is outside ±(2^53 − 1).
95pub fn safe_integer(value: i64) -> Result<i64, BoxDynError> {
96    if (-MAX_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&value) {
97        Ok(value)
98    } else {
99        Err(format!(
100            "{value} is outside ±(2^53 − 1), the integers a JavaScript number holds exactly: \
101             it would be stored as a different integer"
102        )
103        .into())
104    }
105}
106
107impl Value {
108    /// A JavaScript number, as the storage class it most likely came from.
109    ///
110    /// JavaScript has one number type, so `3` and `3.0` arrive the same. A
111    /// whole number in the safe range is taken as an `Integer` -- which is
112    /// why `f64` decodes from `Integer` as well as `Real`. Anything else,
113    /// including whole numbers too large to be exact, is a `Real`.
114    #[must_use]
115    #[allow(
116        clippy::cast_possible_truncation,
117        clippy::cast_precision_loss,
118        reason = "the range check makes both casts exact"
119    )]
120    pub fn from_number(value: f64) -> Self {
121        if value.fract() == 0.0 && value.abs() <= MAX_SAFE_INTEGER as f64 {
122            Value::Integer(value as i64)
123        } else {
124            Value::Real(value)
125        }
126    }
127
128    /// The value's storage class.
129    #[must_use]
130    pub fn type_info(&self) -> TypeInfo {
131        match self {
132            Value::Null => TypeInfo::Null,
133            Value::Integer(_) => TypeInfo::Integer,
134            Value::Real(_) => TypeInfo::Real,
135            Value::Text(_) => TypeInfo::Text,
136            Value::Blob(_) => TypeInfo::Blob,
137        }
138    }
139
140    /// Whether the value is `NULL`.
141    #[must_use]
142    pub fn is_null(&self) -> bool {
143        matches!(self, Value::Null)
144    }
145
146    // The accessors `Decode` implementations use. Each accepts exactly the
147    // storage classes the matching `Type::compatible` does, so `try_get` and
148    // `try_get_unchecked` agree about what decodes.
149
150    /// An `INTEGER`.
151    ///
152    /// # Errors
153    ///
154    /// For any other storage class.
155    pub fn int(&self) -> Result<i64, BoxDynError> {
156        match *self {
157            Value::Integer(value) => Ok(value),
158            ref other => Err(mismatch("an INTEGER", other)),
159        }
160    }
161
162    /// A `REAL`, or an `INTEGER` as one.
163    ///
164    /// # Errors
165    ///
166    /// For any other storage class.
167    #[allow(
168        clippy::cast_precision_loss,
169        reason = "an INTEGER here is within ±(2^53 − 1), so it converts exactly"
170    )]
171    pub fn real(&self) -> Result<f64, BoxDynError> {
172        match *self {
173            Value::Real(value) => Ok(value),
174            Value::Integer(value) => Ok(value as f64),
175            ref other => Err(mismatch("a REAL", other)),
176        }
177    }
178
179    /// `TEXT`.
180    ///
181    /// # Errors
182    ///
183    /// For any other storage class.
184    pub fn text(&self) -> Result<&str, BoxDynError> {
185        match self {
186            Value::Text(value) => Ok(value),
187            other => Err(mismatch("TEXT", other)),
188        }
189    }
190
191    /// A `BLOB`, or `TEXT` as its bytes.
192    ///
193    /// # Errors
194    ///
195    /// For any other storage class.
196    pub fn blob(&self) -> Result<&[u8], BoxDynError> {
197        match self {
198            Value::Blob(value) => Ok(value),
199            // As sqlx-sqlite does: SQLite hands TEXT to `sqlite3_column_blob`
200            // as its bytes.
201            Value::Text(value) => Ok(value.as_bytes()),
202            other => Err(mismatch("a BLOB", other)),
203        }
204    }
205}
206
207fn mismatch(expected: &str, found: &Value) -> BoxDynError {
208    format!("expected {expected}, found {}", found.type_info()).into()
209}
210
211/// The [`Value`] behind a driver's `ValueRef`, which is what the `Decode`
212/// impls [`impl_types!`](crate::impl_types) writes read from.
213pub trait AsValue<'r> {
214    /// The value this reference points at.
215    fn as_value(&self) -> &'r Value;
216}
217
218#[cfg(test)]
219mod tests {
220    use super::{safe_integer, Value, MAX_SAFE_INTEGER};
221
222    #[test]
223    fn whole_numbers_in_the_safe_range_are_integers() {
224        assert_eq!(Value::from_number(3.0), Value::Integer(3));
225        assert_eq!(Value::from_number(-0.0), Value::Integer(0));
226        assert_eq!(
227            Value::from_number(9_007_199_254_740_991.0),
228            Value::Integer(MAX_SAFE_INTEGER)
229        );
230    }
231
232    #[test]
233    fn everything_else_is_real() {
234        assert_eq!(Value::from_number(1.5), Value::Real(1.5));
235        // 2^53: whole, but no longer exact -- 2^53 + 1 would read the same.
236        assert_eq!(
237            Value::from_number(9_007_199_254_740_992.0),
238            Value::Real(9_007_199_254_740_992.0)
239        );
240        assert!(matches!(Value::from_number(f64::INFINITY), Value::Real(_)));
241        assert!(matches!(Value::from_number(f64::NAN), Value::Real(_)));
242    }
243
244    #[test]
245    fn safe_integer_accepts_exactly_the_safe_range() {
246        assert!(safe_integer(MAX_SAFE_INTEGER).is_ok());
247        assert!(safe_integer(-MAX_SAFE_INTEGER).is_ok());
248        assert!(safe_integer(MAX_SAFE_INTEGER + 1).is_err());
249        assert!(safe_integer(-MAX_SAFE_INTEGER - 1).is_err());
250        assert!(safe_integer(i64::MIN).is_err());
251    }
252
253    #[test]
254    fn accessors_accept_what_their_types_are_compatible_with() {
255        assert_eq!(Value::Integer(2).int().unwrap(), 2);
256        assert!(Value::Real(2.0).int().is_err());
257        assert!((Value::Integer(2).real().unwrap() - 2.0).abs() < f64::EPSILON);
258        assert_eq!(Value::Text("ab".into()).blob().unwrap(), b"ab");
259        assert!(Value::Blob(b"ab".to_vec()).text().is_err());
260        assert!(Value::Null.int().is_err());
261    }
262}