1use crate::{DynamicTnid, Tnid, TnidName};
14
15fn u128_to_be_bytes(id: u128) -> [u8; 16] {
16 id.to_be_bytes()
17}
18
19fn be_bytes_to_u128(bytes: &[u8]) -> Result<u128, sqlx::error::BoxDynError> {
20 if bytes.len() != 16 {
21 return Err(Box::new(std::io::Error::new(
22 std::io::ErrorKind::InvalidData,
23 format!("expected 16 bytes, got {}", bytes.len()),
24 )));
25 }
26
27 let mut arr = [0_u8; 16];
28 arr.copy_from_slice(bytes);
29 Ok(u128::from_be_bytes(arr))
30}
31
32#[cfg(feature = "sqlx-postgres")]
33mod postgres {
34 use super::*;
35 use sqlx::encode::{Encode, IsNull};
36 use sqlx::types::Type;
37 use sqlx::Postgres;
38
39 impl<Name: TnidName> Type<Postgres> for Tnid<Name> {
40 fn type_info() -> sqlx::postgres::PgTypeInfo {
41 sqlx::postgres::PgTypeInfo::with_name("UUID")
43 }
44 }
45
46 impl<Name: TnidName> sqlx::postgres::PgHasArrayType for Tnid<Name> {
47 fn array_type_info() -> sqlx::postgres::PgTypeInfo {
48 sqlx::postgres::PgTypeInfo::array_of("UUID")
49 }
50 }
51
52 impl<'q, Name: TnidName> Encode<'q, Postgres> for Tnid<Name> {
53 fn encode_by_ref(
54 &self,
55 buf: &mut sqlx::postgres::PgArgumentBuffer,
56 ) -> Result<IsNull, sqlx::error::BoxDynError> {
57 let bytes = u128_to_be_bytes(self.as_u128());
58 buf.extend_from_slice(&bytes);
59 Ok(IsNull::No)
60 }
61 }
62
63 impl<'r, Name: TnidName> sqlx::decode::Decode<'r, Postgres> for Tnid<Name> {
64 fn decode(value: sqlx::postgres::PgValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
65 let id = match value.format() {
66 sqlx::postgres::PgValueFormat::Binary => be_bytes_to_u128(value.as_bytes()?)?,
67 sqlx::postgres::PgValueFormat::Text => {
68 let s = value.as_str()?;
69 crate::UuidLike::parse_uuid_string(s)
70 .map_err(|e| Box::new(e) as sqlx::error::BoxDynError)?
71 .as_u128()
72 }
73 };
74
75 Tnid::<Name>::from_u128(id).map_err(|e| Box::new(e) as sqlx::error::BoxDynError)
76 }
77 }
78
79 impl Type<Postgres> for DynamicTnid {
80 fn type_info() -> sqlx::postgres::PgTypeInfo {
81 sqlx::postgres::PgTypeInfo::with_name("UUID")
82 }
83 }
84
85 impl sqlx::postgres::PgHasArrayType for DynamicTnid {
86 fn array_type_info() -> sqlx::postgres::PgTypeInfo {
87 sqlx::postgres::PgTypeInfo::array_of("UUID")
88 }
89 }
90
91 impl<'q> Encode<'q, Postgres> for DynamicTnid {
92 fn encode_by_ref(
93 &self,
94 buf: &mut sqlx::postgres::PgArgumentBuffer,
95 ) -> Result<IsNull, sqlx::error::BoxDynError> {
96 let bytes = u128_to_be_bytes(self.as_u128());
97 buf.extend_from_slice(&bytes);
98 Ok(IsNull::No)
99 }
100 }
101
102 impl<'r> sqlx::decode::Decode<'r, Postgres> for DynamicTnid {
103 fn decode(value: sqlx::postgres::PgValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
104 let id = match value.format() {
105 sqlx::postgres::PgValueFormat::Binary => be_bytes_to_u128(value.as_bytes()?)?,
106 sqlx::postgres::PgValueFormat::Text => {
107 let s = value.as_str()?;
108 crate::UuidLike::parse_uuid_string(s)
109 .map_err(|e| Box::new(e) as sqlx::error::BoxDynError)?
110 .as_u128()
111 }
112 };
113
114 DynamicTnid::from_u128(id).map_err(|e| Box::new(e) as sqlx::error::BoxDynError)
115 }
116 }
117}
118
119#[cfg(feature = "sqlx-mysql")]
120mod mysql {
121 use super::*;
122 use sqlx::decode::Decode;
123 use sqlx::encode::{Encode, IsNull};
124 use sqlx::types::Type;
125 use sqlx::MySql;
126
127 impl<Name: TnidName> Type<MySql> for Tnid<Name> {
128 fn type_info() -> sqlx::mysql::MySqlTypeInfo {
129 <&[u8] as Type<MySql>>::type_info()
130 }
131
132 fn compatible(ty: &sqlx::mysql::MySqlTypeInfo) -> bool {
133 <&[u8] as Type<MySql>>::compatible(ty)
134 }
135 }
136
137 impl<'q, Name: TnidName> Encode<'q, MySql> for Tnid<Name> {
138 fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, sqlx::error::BoxDynError> {
139 let bytes = u128_to_be_bytes(self.as_u128());
140 <&[u8] as Encode<MySql>>::encode_by_ref(&bytes.as_slice(), buf)
141 }
142 }
143
144 impl<'r, Name: TnidName> Decode<'r, MySql> for Tnid<Name> {
145 fn decode(value: sqlx::mysql::MySqlValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
146 let bytes = <&[u8] as Decode<MySql>>::decode(value)?;
147 let id = be_bytes_to_u128(bytes)?;
148 Tnid::<Name>::from_u128(id).map_err(|e| Box::new(e) as sqlx::error::BoxDynError)
149 }
150 }
151
152 impl Type<MySql> for DynamicTnid {
153 fn type_info() -> sqlx::mysql::MySqlTypeInfo {
154 <&[u8] as Type<MySql>>::type_info()
155 }
156
157 fn compatible(ty: &sqlx::mysql::MySqlTypeInfo) -> bool {
158 <&[u8] as Type<MySql>>::compatible(ty)
159 }
160 }
161
162 impl<'q> Encode<'q, MySql> for DynamicTnid {
163 fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, sqlx::error::BoxDynError> {
164 let bytes = u128_to_be_bytes(self.as_u128());
165 <&[u8] as Encode<MySql>>::encode_by_ref(&bytes.as_slice(), buf)
166 }
167 }
168
169 impl<'r> Decode<'r, MySql> for DynamicTnid {
170 fn decode(value: sqlx::mysql::MySqlValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
171 let bytes = <&[u8] as Decode<MySql>>::decode(value)?;
172 let id = be_bytes_to_u128(bytes)?;
173 DynamicTnid::from_u128(id).map_err(|e| Box::new(e) as sqlx::error::BoxDynError)
174 }
175 }
176}
177
178#[cfg(feature = "sqlx-sqlite")]
179mod sqlite {
180 use super::*;
181 use sqlx::decode::Decode;
182 use sqlx::encode::{Encode, IsNull};
183 use sqlx::types::Type;
184 use sqlx::Sqlite;
185 use sqlx::TypeInfo;
186 use sqlx::ValueRef;
187 use std::borrow::Cow;
188
189 impl<Name: TnidName> Type<Sqlite> for Tnid<Name> {
190 fn type_info() -> sqlx::sqlite::SqliteTypeInfo {
191 <&[u8] as Type<Sqlite>>::type_info()
192 }
193
194 fn compatible(ty: &sqlx::sqlite::SqliteTypeInfo) -> bool {
195 <&[u8] as Type<Sqlite>>::compatible(ty)
196 }
197 }
198
199 impl<'q, Name: TnidName> Encode<'q, Sqlite> for Tnid<Name> {
200 fn encode_by_ref(
201 &self,
202 args: &mut Vec<sqlx::sqlite::SqliteArgumentValue<'q>>,
203 ) -> Result<IsNull, sqlx::error::BoxDynError> {
204 let bytes = u128_to_be_bytes(self.as_u128());
205 args.push(sqlx::sqlite::SqliteArgumentValue::Blob(Cow::Owned(
206 bytes.to_vec(),
207 )));
208 Ok(IsNull::No)
209 }
210 }
211
212 impl<'r, Name: TnidName> Decode<'r, Sqlite> for Tnid<Name> {
213 fn decode(
214 value: sqlx::sqlite::SqliteValueRef<'r>,
215 ) -> Result<Self, sqlx::error::BoxDynError> {
216 let ty_name = value.type_info().name().to_ascii_uppercase();
217
218 let id = if ty_name == "TEXT" {
219 let s = <&str as Decode<Sqlite>>::decode(value)?;
220 if s.as_bytes().contains(&b'.') {
221 Tnid::<Name>::parse_tnid_string(s)
222 .map_err(|e| Box::new(e) as sqlx::error::BoxDynError)?
223 .as_u128()
224 } else {
225 Tnid::<Name>::parse_uuid_string(s)
226 .map_err(|e| Box::new(e) as sqlx::error::BoxDynError)?
227 .as_u128()
228 }
229 } else {
230 let bytes = <&[u8] as Decode<Sqlite>>::decode(value)?;
231 be_bytes_to_u128(bytes)?
232 };
233
234 Tnid::<Name>::from_u128(id).map_err(|e| Box::new(e) as sqlx::error::BoxDynError)
235 }
236 }
237
238 impl Type<Sqlite> for DynamicTnid {
239 fn type_info() -> sqlx::sqlite::SqliteTypeInfo {
240 <&[u8] as Type<Sqlite>>::type_info()
241 }
242
243 fn compatible(ty: &sqlx::sqlite::SqliteTypeInfo) -> bool {
244 <&[u8] as Type<Sqlite>>::compatible(ty)
245 }
246 }
247
248 impl<'q> Encode<'q, Sqlite> for DynamicTnid {
249 fn encode_by_ref(
250 &self,
251 args: &mut Vec<sqlx::sqlite::SqliteArgumentValue<'q>>,
252 ) -> Result<IsNull, sqlx::error::BoxDynError> {
253 let bytes = u128_to_be_bytes(self.as_u128());
254 args.push(sqlx::sqlite::SqliteArgumentValue::Blob(Cow::Owned(
255 bytes.to_vec(),
256 )));
257 Ok(IsNull::No)
258 }
259 }
260
261 impl<'r> Decode<'r, Sqlite> for DynamicTnid {
262 fn decode(
263 value: sqlx::sqlite::SqliteValueRef<'r>,
264 ) -> Result<Self, sqlx::error::BoxDynError> {
265 let ty_name = value.type_info().name().to_ascii_uppercase();
266
267 let id = if ty_name == "TEXT" {
268 let s = <&str as Decode<Sqlite>>::decode(value)?;
269 if s.as_bytes().contains(&b'.') {
270 DynamicTnid::parse_tnid_string(s)
271 .map_err(|e| Box::new(e) as sqlx::error::BoxDynError)?
272 .as_u128()
273 } else {
274 crate::UuidLike::parse_uuid_string(s)
275 .map_err(|e| Box::new(e) as sqlx::error::BoxDynError)?
276 .as_u128()
277 }
278 } else {
279 let bytes = <&[u8] as Decode<Sqlite>>::decode(value)?;
280 be_bytes_to_u128(bytes)?
281 };
282
283 DynamicTnid::from_u128(id).map_err(|e| Box::new(e) as sqlx::error::BoxDynError)
284 }
285 }
286}