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(
39        &self,
40        buf: &mut Vec<SpgArgumentValue<'q>>,
41    ) -> Result<IsNull, BoxDynError> {
42        // PG external form `{1,2,3}`.
43        let mut s = String::from("{");
44        for (i, v) in self.iter().enumerate() {
45            if i > 0 {
46                s.push(',');
47            }
48            s.push_str(&v.to_string());
49        }
50        s.push('}');
51        buf.push(SpgArgumentValue {
52            value: EngineValue::Text(s),
53            type_info: Some(<Vec<i32> as Type<Spg>>::type_info()),
54            _phantom: core::marker::PhantomData,
55        });
56        Ok(IsNull::No)
57    }
58}
59
60impl<'r> Decode<'r, Spg> for Vec<i32> {
61    fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
62        match value.engine() {
63            EngineValue::IntArray(items) => {
64                Ok(items.iter().filter_map(|o| o.clone()).collect())
65            }
66            // BIGINT[] narrows lossily.
67            EngineValue::BigIntArray(items) => Ok(items
68                .iter()
69                .filter_map(|o| o.clone().and_then(|n| i32::try_from(n).ok()))
70                .collect()),
71            other => Err(format!("cannot decode {other:?} as Vec<i32>").into()),
72        }
73    }
74}
75
76// ---- Vec<i64> / BIGINT[] ----
77
78impl Type<Spg> for Vec<i64> {
79    fn type_info() -> SpgTypeInfo {
80        SpgTypeInfo::of(Kind::Text)
81    }
82    fn compatible(_ty: &SpgTypeInfo) -> bool {
83        true
84    }
85}
86
87impl<'q> Encode<'q, Spg> for Vec<i64> {
88    fn encode_by_ref(
89        &self,
90        buf: &mut Vec<SpgArgumentValue<'q>>,
91    ) -> Result<IsNull, BoxDynError> {
92        let mut s = String::from("{");
93        for (i, v) in self.iter().enumerate() {
94            if i > 0 {
95                s.push(',');
96            }
97            s.push_str(&v.to_string());
98        }
99        s.push('}');
100        buf.push(SpgArgumentValue {
101            value: EngineValue::Text(s),
102            type_info: Some(<Vec<i64> as Type<Spg>>::type_info()),
103            _phantom: core::marker::PhantomData,
104        });
105        Ok(IsNull::No)
106    }
107}
108
109impl<'r> Decode<'r, Spg> for Vec<i64> {
110    fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
111        match value.engine() {
112            EngineValue::BigIntArray(items) => {
113                Ok(items.iter().filter_map(|o| o.clone()).collect())
114            }
115            EngineValue::IntArray(items) => Ok(items
116                .iter()
117                .filter_map(|o| o.clone().map(i64::from))
118                .collect()),
119            other => Err(format!("cannot decode {other:?} as Vec<i64>").into()),
120        }
121    }
122}
123
124// ---- Vec<String> / TEXT[] ----
125
126impl Type<Spg> for Vec<String> {
127    fn type_info() -> SpgTypeInfo {
128        SpgTypeInfo::of(Kind::Text)
129    }
130    fn compatible(_ty: &SpgTypeInfo) -> bool {
131        true
132    }
133}
134
135impl<'q> Encode<'q, Spg> for Vec<String> {
136    fn encode_by_ref(
137        &self,
138        buf: &mut Vec<SpgArgumentValue<'q>>,
139    ) -> Result<IsNull, BoxDynError> {
140        // PG external form `{"a","b","c"}` — quote every element
141        // so commas and braces inside payloads survive.
142        let mut s = String::from("{");
143        for (i, v) in self.iter().enumerate() {
144            if i > 0 {
145                s.push(',');
146            }
147            s.push('"');
148            for ch in v.chars() {
149                if ch == '\\' || ch == '"' {
150                    s.push('\\');
151                }
152                s.push(ch);
153            }
154            s.push('"');
155        }
156        s.push('}');
157        buf.push(SpgArgumentValue {
158            value: EngineValue::Text(s),
159            type_info: Some(<Vec<String> as Type<Spg>>::type_info()),
160            _phantom: core::marker::PhantomData,
161        });
162        Ok(IsNull::No)
163    }
164}
165
166impl<'r> Decode<'r, Spg> for Vec<String> {
167    fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
168        match value.engine() {
169            EngineValue::TextArray(items) => {
170                Ok(items.iter().filter_map(|o| o.clone()).collect())
171            }
172            other => Err(format!("cannot decode {other:?} as Vec<String>").into()),
173        }
174    }
175}