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}