Skip to main content

spg_sqlx/
type_info.rs

1//! v7.16.0 — `sqlx::TypeInfo` for SPG column types.
2
3use std::fmt;
4
5use sqlx_core::type_info::TypeInfo;
6
7/// SPG column type info. Stores the concrete [`Kind`] so the
8/// adapter can drive PG-shape column metadata that
9/// `#[derive(FromRow)]` expects.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct SpgTypeInfo {
12    kind: Kind,
13}
14
15/// Identity tag for each column type the adapter currently
16/// understands. Matches the subset of `spg_storage::DataType`
17/// the adapter Encode/Decode coverage extends to.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[non_exhaustive]
20pub enum Kind {
21    /// `INT` / 4-byte signed integer.
22    Int,
23    /// `BIGINT` / 8-byte signed integer.
24    BigInt,
25    /// `SMALLINT` / 2-byte signed integer.
26    SmallInt,
27    /// `BOOLEAN`.
28    Bool,
29    /// `TEXT` / `VARCHAR` (text body — encoding agnostic).
30    Text,
31    /// `BYTEA` (raw bytes).
32    Bytes,
33    /// `FLOAT` (IEEE-754 double).
34    Float,
35    /// `DATE`.
36    Date,
37    /// `TIMESTAMP`.
38    Timestamp,
39    /// `TIMESTAMPTZ`.
40    Timestamptz,
41    /// `JSON` / `JSONB` (text-backed JSON).
42    Json,
43    /// v7.17.0 — `UUID` (128-bit identifier, RFC 4122 byte order).
44    /// Bridges decode into `uuid::Uuid` or `String` (canonical
45    /// hyphenated lowercase form).
46    Uuid,
47    /// v7.17.0 Phase 3.P0-32 — `TIME` (without time zone). i64
48    /// microseconds since 00:00:00. Bridges decode into `String`
49    /// (canonical `HH:MM:SS[.ffffff]` form).
50    Time,
51    /// v7.17.0 Phase 3.P0-33 — MySQL `YEAR`. u16 in 1901..=2155
52    /// plus zero-year sentinel. Bridges decode into `i32` (wire
53    /// shape collapses to INT4) or `String` (4-digit zero-pad).
54    Year,
55    /// v7.17.0 Phase 3.P0-34 — PG `TIMETZ` (TIME WITH TIME
56    /// ZONE). i64 us since 00:00:00 local + i32 offset_secs.
57    /// Bridges decode into `String` (canonical
58    /// `HH:MM:SS[.ffffff]±HH[:MM]`).
59    TimeTz,
60    /// v7.17.0 Phase 3.P0-35 — PG `MONEY` — i64 cents.
61    /// Bridges decode into `String` (canonical en_US
62    /// `$N,NNN.CC`) or `i64` (raw cents).
63    Money,
64    /// v7.17.0 Phase 3.P0-38 — PG range types (int4range /
65    /// int8range / numrange / tsrange / tstzrange / daterange).
66    /// Bridges decode into `String` (canonical `[a,b)`).
67    Range,
68    /// v7.17.0 Phase 3.P0-39 — PG `hstore` extension type.
69    /// Bridges decode into `String` (canonical `"k"=>"v"`)
70    /// or `HashMap<String, Option<String>>` (in the language
71    /// dialect that ships the hstore feature).
72    Hstore,
73    /// v7.17.0 Phase 3.P0-67 — PG `NUMERIC(p, s)` / `DECIMAL(p, s)`
74    /// — exact-decimal fixed-point. Stored engine-side as
75    /// `(scaled: i128, scale: u8)`. Bridges decode into
76    /// `bigdecimal::BigDecimal` (under the `bigdecimal`
77    /// feature) or `String` (canonical PG decimal text).
78    Numeric,
79    /// v7.17.0 Phase 3.P0-68 — pgvector `VECTOR(N)` (any of the
80    /// three storage encodings: default f32, `USING SQ8`,
81    /// `USING HALF`). Bridges decode into `Vec<f32>` or
82    /// `String` (canonical pgvector external form
83    /// `'[1, 2.5, -3]'`). Quantised storage variants
84    /// (`Sq8Vector` / `HalfVector`) dequantise to f32 at the
85    /// adapter boundary.
86    Vector,
87    /// v7.17.0 Phase 3.P0-68 — PG `TSVECTOR` (full-text search
88    /// document representation). Bridges decode into `String`
89    /// (canonical PG external form
90    /// `'word1':1 'word2':2,3A'`). Encode is intentionally not
91    /// supported — clients build `tsvector` via the `to_tsvector`
92    /// SQL function, not by binding raw lexeme lists.
93    TsVector,
94    /// Unknown / type-erased — used for parameters that the
95    /// adapter binds without a fixed column-side type yet (e.g.
96    /// the first bind of a fresh parameter index).
97    Null,
98}
99
100impl SpgTypeInfo {
101    /// Construct a TypeInfo for a known kind.
102    #[must_use]
103    pub const fn of(kind: Kind) -> Self {
104        Self { kind }
105    }
106
107    /// The concrete kind tag.
108    #[must_use]
109    pub const fn kind(&self) -> Kind {
110        self.kind
111    }
112
113    /// v7.16.0 — translate from the engine's [`DataType`] to
114    /// the adapter's typed `Kind`. Used by the fetch path to
115    /// build column metadata for `SpgRow`. Any DataType the
116    /// adapter hasn't bridged yet maps to `Kind::Null` —
117    /// downstream Decode calls then fail with a clear
118    /// "cannot decode" message identifying the column type.
119    #[must_use]
120    pub fn from_data_type(ty: spg_embedded::DataType) -> Self {
121        use spg_embedded::DataType;
122        let kind = match ty {
123            DataType::Int => Kind::Int,
124            DataType::BigInt => Kind::BigInt,
125            DataType::SmallInt => Kind::SmallInt,
126            DataType::Bool => Kind::Bool,
127            DataType::Text => Kind::Text,
128            DataType::Bytes => Kind::Bytes,
129            DataType::Float => Kind::Float,
130            DataType::Date => Kind::Date,
131            DataType::Timestamp => Kind::Timestamp,
132            DataType::Timestamptz => Kind::Timestamptz,
133            DataType::Json => Kind::Json,
134            // v7.17.0 — UUID bridges to `uuid::Uuid` (when the
135            // `uuid` feature is enabled on sqlx) or to `String`.
136            DataType::Uuid => Kind::Uuid,
137            DataType::Time => Kind::Time,
138            DataType::Year => Kind::Year,
139            DataType::TimeTz => Kind::TimeTz,
140            DataType::Money => Kind::Money,
141            DataType::Range(_) => Kind::Range,
142            DataType::Hstore => Kind::Hstore,
143            // v7.17.0 Phase 3.P0-67 — NUMERIC(p, s) → exact-decimal.
144            DataType::Numeric { .. } => Kind::Numeric,
145            // v7.17.0 Phase 3.P0-68 — pgvector + tsvector.
146            DataType::Vector { .. } => Kind::Vector,
147            DataType::TsVector => Kind::TsVector,
148            // v7.17.0 Phase 3.P0-40 — 2D arrays decode as TEXT
149            // on the sqlx side (canonical PG nested external form).
150            DataType::IntArray2D | DataType::BigIntArray2D | DataType::TextArray2D => Kind::Text,
151            // v7.16.0 — DataType is #[non_exhaustive]; any
152            // variant we haven't bridged yet decodes to Null
153            // (so Decode impls see "compatible? no" instead of
154            // a panic).
155            _ => Kind::Null,
156        };
157        Self { kind }
158    }
159}
160
161impl TypeInfo for SpgTypeInfo {
162    fn is_null(&self) -> bool {
163        matches!(self.kind, Kind::Null)
164    }
165
166    fn name(&self) -> &str {
167        match self.kind {
168            Kind::Int => "INT",
169            Kind::BigInt => "BIGINT",
170            Kind::SmallInt => "SMALLINT",
171            Kind::Bool => "BOOLEAN",
172            Kind::Text => "TEXT",
173            Kind::Bytes => "BYTEA",
174            Kind::Float => "FLOAT",
175            Kind::Date => "DATE",
176            Kind::Timestamp => "TIMESTAMP",
177            Kind::Timestamptz => "TIMESTAMPTZ",
178            Kind::Json => "JSON",
179            Kind::Uuid => "UUID",
180            Kind::Time => "TIME",
181            Kind::Year => "YEAR",
182            Kind::TimeTz => "TIMETZ",
183            Kind::Money => "MONEY",
184            Kind::Range => "RANGE",
185            Kind::Hstore => "HSTORE",
186            Kind::Numeric => "NUMERIC",
187            Kind::Vector => "VECTOR",
188            Kind::TsVector => "TSVECTOR",
189            Kind::Null => "NULL",
190        }
191    }
192}
193
194impl fmt::Display for SpgTypeInfo {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        f.write_str(self.name())
197    }
198}