Skip to main content

spg_sqlx/
value.rs

1//! v7.16.0 — `sqlx::Value` + `sqlx::ValueRef` for SPG cells.
2
3use std::borrow::Cow;
4
5use sqlx_core::error::BoxDynError;
6use sqlx_core::value::{Value, ValueRef};
7
8use spg_embedded::ValueOwned as EngineValue;
9
10use crate::database::Spg;
11use crate::type_info::{Kind, SpgTypeInfo};
12
13/// Owned form of an SPG cell as it comes back from a query.
14/// Wraps [`spg_embedded::Value`] + the column's static
15/// [`SpgTypeInfo`] so the decode path can drive sqlx's type-
16/// compatibility check (`Decode::compatible`).
17#[derive(Debug, Clone)]
18pub struct SpgValue {
19    inner: EngineValue,
20    type_info: SpgTypeInfo,
21}
22
23impl SpgValue {
24    /// Wrap an engine value with its column type info.
25    #[must_use]
26    pub fn new(inner: EngineValue, type_info: SpgTypeInfo) -> Self {
27        Self { inner, type_info }
28    }
29
30    /// Borrow the underlying engine value.
31    #[must_use]
32    pub const fn engine(&self) -> &EngineValue {
33        &self.inner
34    }
35}
36
37impl Value for SpgValue {
38    type Database = Spg;
39
40    fn as_ref(&self) -> SpgValueRef<'_> {
41        SpgValueRef::new(&self.inner, self.type_info.clone())
42    }
43
44    fn type_info(&self) -> Cow<'_, SpgTypeInfo> {
45        Cow::Borrowed(&self.type_info)
46    }
47
48    fn is_null(&self) -> bool {
49        matches!(self.inner, EngineValue::Null)
50    }
51}
52
53/// Borrowed form of an SPG cell. Returned by [`SpgRow::try_get_raw`][crate::row::SpgRow]
54/// to let Decode implementations read the value without taking
55/// ownership.
56#[derive(Debug, Clone)]
57pub struct SpgValueRef<'r> {
58    inner: &'r EngineValue,
59    type_info: SpgTypeInfo,
60}
61
62impl<'r> SpgValueRef<'r> {
63    /// Build a borrowed cell + its column type info.
64    #[must_use]
65    pub fn new(inner: &'r EngineValue, type_info: SpgTypeInfo) -> Self {
66        Self { inner, type_info }
67    }
68
69    /// Borrow the underlying engine value.
70    #[must_use]
71    pub const fn engine(&self) -> &'r EngineValue {
72        self.inner
73    }
74
75    /// The column kind tag for this value.
76    #[must_use]
77    pub const fn kind(&self) -> Kind {
78        self.type_info.kind()
79    }
80}
81
82impl<'r> ValueRef<'r> for SpgValueRef<'r> {
83    type Database = Spg;
84
85    fn to_owned(&self) -> SpgValue {
86        SpgValue {
87            inner: self.inner.clone(),
88            type_info: self.type_info.clone(),
89        }
90    }
91
92    fn type_info(&self) -> Cow<'_, SpgTypeInfo> {
93        Cow::Borrowed(&self.type_info)
94    }
95
96    fn is_null(&self) -> bool {
97        matches!(self.inner, EngineValue::Null)
98    }
99}
100
101/// Map an engine value's runtime variant to the corresponding
102/// [`Kind`] tag — used when a fetched row carries cells whose
103/// column metadata is not yet known to the adapter.
104#[must_use]
105#[allow(dead_code)]
106pub fn engine_value_kind(v: &EngineValue) -> Kind {
107    match v {
108        EngineValue::Null => Kind::Null,
109        EngineValue::SmallInt(_) => Kind::SmallInt,
110        EngineValue::Int(_) => Kind::Int,
111        EngineValue::BigInt(_) => Kind::BigInt,
112        EngineValue::Bool(_) => Kind::Bool,
113        EngineValue::Text(_) => Kind::Text,
114        EngineValue::Bytes(_) => Kind::Bytes,
115        EngineValue::Float(_) => Kind::Float,
116        // v7.39 (round 269) — REAL rides the same Kind as FLOAT so
117        // f32 / f64 stay `compatible` with a real column; the width
118        // difference lives in Decode, not in the kind. Without this it
119        // fell to the non-exhaustive catch-all and reported Kind::Null,
120        // which fails every type check.
121        EngineValue::Real(_) => Kind::Float,
122        EngineValue::Date(_) => Kind::Date,
123        EngineValue::Timestamp(_) => Kind::Timestamp,
124        EngineValue::Json(_) => Kind::Json,
125        // v7.17.0 Phase 3.P0-69 — UUID bridges to uuid::Uuid /
126        // String.
127        EngineValue::Uuid(_) => Kind::Uuid,
128        // v7.17.0 Phase 3.P0-67 — NUMERIC bridges to bigdecimal /
129        // String.
130        EngineValue::Numeric { .. } => Kind::Numeric,
131        // v7.17.0 Phase 3.P0-68 — pgvector (all encodings) /
132        // tsvector. Sq8 + Half storage variants dequantise to
133        // f32 inside Decode, so the column-side kind is still
134        // Vector.
135        EngineValue::Vector(_) | EngineValue::Sq8Vector(_) | EngineValue::HalfVector(_) => {
136            Kind::Vector
137        }
138        EngineValue::TsVector(_) => Kind::TsVector,
139        // v7.16.0 — non-exhaustive enum; future types
140        // (Interval, arrays beyond text/int/bigint, hstore) decode
141        // to Kind::Null until the corresponding Decode lands.
142        _ => Kind::Null,
143    }
144}
145
146/// Force a no-op use of [`BoxDynError`] so future Decode
147/// impls can return an unboxed error without losing the
148/// import path.
149#[allow(dead_code)]
150fn _box_dyn_marker() -> Option<BoxDynError> {
151    None
152}