Skip to main content

spg_sqlx/types/
array.rs

1//! v7.16.0 — `Type` / `Encode` / `Decode` for the basic array
2//! shapes mailrs uses: `Vec<i32>` (INT[]), `Vec<i64>` (BIGINT[]),
3//! `Vec<String>` (TEXT[]). NULL elements are NOT supported on
4//! Encode (mailrs's existing PG path stores Vec<T>, not
5//! Vec<Option<T>>); Decode tolerates NULLs by skipping the slot.
6//! Round-trip through SPG's text-form `{a,b,c}` external array
7//! shape.
8
9use sqlx_core::decode::Decode;
10use sqlx_core::encode::{Encode, IsNull};
11use sqlx_core::error::BoxDynError;
12use sqlx_core::types::Type;
13
14use spg_embedded::Value as EngineValue;
15
16use crate::arguments::SpgArgumentValue;
17use crate::database::Spg;
18use crate::type_info::{Kind, SpgTypeInfo};
19use crate::value::SpgValueRef;
20
21// ---- Vec<i32> / INT[] ----
22
23impl Type<Spg> for Vec<i32> {
24    fn type_info() -> SpgTypeInfo {
25        // No dedicated array kind in the v7.16.0 type_info — the
26        // engine stores arrays as IntArray/BigIntArray/TextArray
27        // variants, dispatch happens at coerce time. Surface as
28        // Text so sqlx's type-compatibility check passes for the
29        // PG `INT[]` column type.
30        SpgTypeInfo::of(Kind::Text)
31    }
32    fn compatible(_ty: &SpgTypeInfo) -> bool {
33        true
34    }
35}
36
37impl<'q> Encode<'q, Spg> for Vec<i32> {
38    fn encode_by_ref(&self, buf: &mut Vec<SpgArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
39        // PG external form `{1,2,3}`.
40        let mut s = String::from("{");
41        for (i, v) in self.iter().enumerate() {
42            if i > 0 {
43                s.push(',');
44            }
45            s.push_str(&v.to_string());
46        }
47        s.push('}');
48        buf.push(SpgArgumentValue {
49            value: EngineValue::Text(s),
50            type_info: Some(<Vec<i32> as Type<Spg>>::type_info()),
51            _phantom: core::marker::PhantomData,
52        });
53        Ok(IsNull::No)
54    }
55}
56
57impl<'r> Decode<'r, Spg> for Vec<i32> {
58    fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
59        match value.engine() {
60            EngineValue::IntArray(items) => Ok(items.iter().filter_map(|o| *o).collect()),
61            // BIGINT[] narrows lossily.
62            EngineValue::BigIntArray(items) => Ok(items
63                .iter()
64                .filter_map(|o| (*o).and_then(|n| i32::try_from(n).ok()))
65                .collect()),
66            other => Err(format!("cannot decode {other:?} as Vec<i32>").into()),
67        }
68    }
69}
70
71// ---- Vec<i64> / BIGINT[] ----
72
73impl Type<Spg> for Vec<i64> {
74    fn type_info() -> SpgTypeInfo {
75        SpgTypeInfo::of(Kind::Text)
76    }
77    fn compatible(_ty: &SpgTypeInfo) -> bool {
78        true
79    }
80}
81
82impl<'q> Encode<'q, Spg> for Vec<i64> {
83    fn encode_by_ref(&self, buf: &mut Vec<SpgArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
84        let mut s = String::from("{");
85        for (i, v) in self.iter().enumerate() {
86            if i > 0 {
87                s.push(',');
88            }
89            s.push_str(&v.to_string());
90        }
91        s.push('}');
92        buf.push(SpgArgumentValue {
93            value: EngineValue::Text(s),
94            type_info: Some(<Vec<i64> as Type<Spg>>::type_info()),
95            _phantom: core::marker::PhantomData,
96        });
97        Ok(IsNull::No)
98    }
99}
100
101impl<'r> Decode<'r, Spg> for Vec<i64> {
102    fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
103        match value.engine() {
104            EngineValue::BigIntArray(items) => Ok(items.iter().filter_map(|o| *o).collect()),
105            EngineValue::IntArray(items) => {
106                Ok(items.iter().filter_map(|o| (*o).map(i64::from)).collect())
107            }
108            other => Err(format!("cannot decode {other:?} as Vec<i64>").into()),
109        }
110    }
111}
112
113// ---- Vec<String> / TEXT[] ----
114
115impl Type<Spg> for Vec<String> {
116    fn type_info() -> SpgTypeInfo {
117        SpgTypeInfo::of(Kind::Text)
118    }
119    fn compatible(_ty: &SpgTypeInfo) -> bool {
120        true
121    }
122}
123
124impl<'q> Encode<'q, Spg> for Vec<String> {
125    fn encode_by_ref(&self, buf: &mut Vec<SpgArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
126        // PG external form `{"a","b","c"}` — quote every element
127        // so commas and braces inside payloads survive.
128        let mut s = String::from("{");
129        for (i, v) in self.iter().enumerate() {
130            if i > 0 {
131                s.push(',');
132            }
133            s.push('"');
134            for ch in v.chars() {
135                if ch == '\\' || ch == '"' {
136                    s.push('\\');
137                }
138                s.push(ch);
139            }
140            s.push('"');
141        }
142        s.push('}');
143        buf.push(SpgArgumentValue {
144            value: EngineValue::Text(s),
145            type_info: Some(<Vec<String> as Type<Spg>>::type_info()),
146            _phantom: core::marker::PhantomData,
147        });
148        Ok(IsNull::No)
149    }
150}
151
152impl<'r> Decode<'r, Spg> for Vec<String> {
153    fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
154        match value.engine() {
155            EngineValue::TextArray(items) => Ok(items.iter().filter_map(|o| o.clone()).collect()),
156            other => Err(format!("cannot decode {other:?} as Vec<String>").into()),
157        }
158    }
159}