eva_common/
db.rs

1/// Database functions (sqlx-based) and dynamic wrapper
2/// Currently supported sqlx 0.6 only
3///
4/// Supported databases: Sqlite, PostgresSQL
5///
6/// For Value type use JSONB only
7/// For OID use VARCHAR(1024)
8///
9/// For Time (feature "time" enabled) type use INTEGER for Sqlite and TIMESTAMP/TIMESTAMPTZ for
10/// Postgres
11#[cfg(feature = "acl")]
12use crate::acl::OIDMask;
13use crate::{OID, value::Value};
14use sqlx::encode::IsNull;
15use sqlx::error::BoxDynError;
16use sqlx::postgres;
17use sqlx::postgres::PgValueRef;
18use sqlx::sqlite;
19use sqlx::sqlite::SqliteValueRef;
20use sqlx::{Decode, Encode};
21use sqlx::{Postgres, Sqlite, Type};
22use std::borrow::Cow;
23
24type ResultIsNull = Result<IsNull, Box<dyn std::error::Error + Send + Sync + 'static>>;
25
26impl Type<Sqlite> for OID {
27    fn type_info() -> sqlite::SqliteTypeInfo {
28        <str as Type<Sqlite>>::type_info()
29    }
30}
31
32impl Type<Postgres> for OID {
33    fn type_info() -> postgres::PgTypeInfo {
34        <str as Type<Postgres>>::type_info()
35    }
36    fn compatible(ty: &postgres::PgTypeInfo) -> bool {
37        *ty == postgres::PgTypeInfo::with_name("VARCHAR")
38            || *ty == postgres::PgTypeInfo::with_name("TEXT")
39    }
40}
41
42impl postgres::PgHasArrayType for OID {
43    fn array_type_info() -> postgres::PgTypeInfo {
44        postgres::PgTypeInfo::with_name("_TEXT")
45    }
46
47    fn array_compatible(ty: &postgres::PgTypeInfo) -> bool {
48        *ty == postgres::PgTypeInfo::with_name("_TEXT")
49            || *ty == postgres::PgTypeInfo::with_name("_VARCHAR")
50    }
51}
52
53impl<'r> Decode<'r, Sqlite> for OID {
54    fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
55        let value = <&str as Decode<Sqlite>>::decode(value)?;
56        value.parse().map_err(Into::into)
57    }
58}
59
60impl<'r> Decode<'r, Postgres> for OID {
61    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
62        let value = <&str as Decode<Postgres>>::decode(value)?;
63        value.parse().map_err(Into::into)
64    }
65}
66
67impl<'q> Encode<'q, Sqlite> for OID {
68    fn encode(self, args: &mut Vec<sqlite::SqliteArgumentValue<'q>>) -> ResultIsNull {
69        args.push(sqlite::SqliteArgumentValue::Text(Cow::Owned(
70            self.to_string(),
71        )));
72
73        Ok(IsNull::No)
74    }
75    fn encode_by_ref(&self, args: &mut Vec<sqlite::SqliteArgumentValue<'q>>) -> ResultIsNull {
76        args.push(sqlite::SqliteArgumentValue::Text(Cow::Owned(
77            self.to_string(),
78        )));
79        Ok(IsNull::No)
80    }
81
82    fn size_hint(&self) -> usize {
83        self.as_str().len()
84    }
85}
86
87impl Encode<'_, Postgres> for OID {
88    fn encode_by_ref(&self, buf: &mut postgres::PgArgumentBuffer) -> ResultIsNull {
89        <&str as Encode<Postgres>>::encode(self.as_str(), buf)
90    }
91    fn size_hint(&self) -> usize {
92        self.as_str().len()
93    }
94}
95
96#[cfg(feature = "acl")]
97impl Type<Sqlite> for OIDMask {
98    fn type_info() -> sqlite::SqliteTypeInfo {
99        <str as Type<Sqlite>>::type_info()
100    }
101}
102
103#[cfg(feature = "acl")]
104impl Type<Postgres> for OIDMask {
105    fn type_info() -> postgres::PgTypeInfo {
106        <str as Type<Postgres>>::type_info()
107    }
108    fn compatible(ty: &postgres::PgTypeInfo) -> bool {
109        *ty == postgres::PgTypeInfo::with_name("VARCHAR")
110            || *ty == postgres::PgTypeInfo::with_name("TEXT")
111    }
112}
113
114#[cfg(feature = "acl")]
115impl postgres::PgHasArrayType for OIDMask {
116    fn array_type_info() -> postgres::PgTypeInfo {
117        postgres::PgTypeInfo::with_name("_TEXT")
118    }
119
120    fn array_compatible(ty: &postgres::PgTypeInfo) -> bool {
121        *ty == postgres::PgTypeInfo::with_name("_TEXT")
122            || *ty == postgres::PgTypeInfo::with_name("_VARCHAR")
123    }
124}
125
126#[cfg(feature = "acl")]
127impl<'r> Decode<'r, Sqlite> for OIDMask {
128    fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
129        let value = <&str as Decode<Sqlite>>::decode(value)?;
130        value.parse().map_err(Into::into)
131    }
132}
133
134#[cfg(feature = "acl")]
135impl<'r> Decode<'r, Postgres> for OIDMask {
136    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
137        let value = <&str as Decode<Postgres>>::decode(value)?;
138        value.parse().map_err(Into::into)
139    }
140}
141
142#[cfg(feature = "acl")]
143impl<'q> Encode<'q, Sqlite> for OIDMask {
144    fn encode(self, args: &mut Vec<sqlite::SqliteArgumentValue<'q>>) -> ResultIsNull {
145        args.push(sqlite::SqliteArgumentValue::Text(Cow::Owned(
146            self.to_string(),
147        )));
148
149        Ok(IsNull::No)
150    }
151    fn encode_by_ref(&self, args: &mut Vec<sqlite::SqliteArgumentValue<'q>>) -> ResultIsNull {
152        args.push(sqlite::SqliteArgumentValue::Text(Cow::Owned(
153            self.to_string(),
154        )));
155        Ok(IsNull::No)
156    }
157}
158
159#[cfg(feature = "acl")]
160impl Encode<'_, Postgres> for OIDMask {
161    #[allow(clippy::needless_borrows_for_generic_args)]
162    fn encode_by_ref(&self, buf: &mut postgres::PgArgumentBuffer) -> ResultIsNull {
163        <&str as Encode<Postgres>>::encode(&self.to_string(), buf)
164    }
165}
166
167impl Type<Sqlite> for Value {
168    fn type_info() -> sqlite::SqliteTypeInfo {
169        <str as Type<Sqlite>>::type_info()
170    }
171
172    fn compatible(ty: &sqlite::SqliteTypeInfo) -> bool {
173        <&str as Type<Sqlite>>::compatible(ty)
174    }
175}
176
177impl Type<Postgres> for Value {
178    fn type_info() -> postgres::PgTypeInfo {
179        postgres::PgTypeInfo::with_name("JSONB")
180    }
181}
182
183impl Encode<'_, Sqlite> for Value {
184    fn encode_by_ref(&self, buf: &mut Vec<sqlite::SqliteArgumentValue<'_>>) -> ResultIsNull {
185        let json_string_value =
186            serde_json::to_string(self).expect("serde_json failed to convert to string");
187        Encode::<Sqlite>::encode(json_string_value, buf)
188    }
189}
190
191impl<'r> Decode<'r, Sqlite> for Value {
192    fn decode(value: sqlite::SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
193        let string_value = <&str as Decode<Sqlite>>::decode(value)?;
194
195        serde_json::from_str(string_value).map_err(Into::into)
196    }
197}
198
199impl Encode<'_, Postgres> for Value {
200    fn encode_by_ref(&self, buf: &mut postgres::PgArgumentBuffer) -> ResultIsNull {
201        buf.push(1);
202        serde_json::to_writer(&mut **buf, &self)
203            .expect("failed to serialize to JSON for encoding on transmission to the database");
204        Ok(IsNull::No)
205    }
206}
207
208impl<'r> Decode<'r, Postgres> for Value {
209    fn decode(value: postgres::PgValueRef<'r>) -> Result<Self, BoxDynError> {
210        let buf = value.as_bytes()?;
211        assert_eq!(buf[0], 1, "unsupported JSONB format version {}", buf[0]);
212        serde_json::from_slice(&buf[1..]).map_err(Into::into)
213    }
214}
215
216#[cfg(feature = "time")]
217mod time_impl {
218    use super::ResultIsNull;
219    use crate::time::Time;
220    use sqlx::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueRef};
221    use sqlx::sqlite::{SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef};
222    use sqlx::{Decode, Encode, Postgres, Sqlite, Type, encode::IsNull, error::BoxDynError};
223
224    const J2000_EPOCH_US: i64 = 946_684_800_000_000;
225
226    impl Type<Sqlite> for Time {
227        fn type_info() -> SqliteTypeInfo {
228            <i64 as Type<Sqlite>>::type_info()
229        }
230
231        fn compatible(ty: &SqliteTypeInfo) -> bool {
232            *ty == <i64 as Type<Sqlite>>::type_info()
233                || *ty == <i32 as Type<Sqlite>>::type_info()
234                || *ty == <i16 as Type<Sqlite>>::type_info()
235                || *ty == <i8 as Type<Sqlite>>::type_info()
236        }
237    }
238
239    impl<'q> Encode<'q, Sqlite> for Time {
240        fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> ResultIsNull {
241            args.push(SqliteArgumentValue::Int64(
242                i64::try_from(self.timestamp_ns()).expect("timestamp too large"),
243            ));
244
245            Ok(IsNull::No)
246        }
247    }
248
249    impl<'r> Decode<'r, Sqlite> for Time {
250        fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
251            let value = <i64 as Decode<Sqlite>>::decode(value)?;
252            Ok(Time::from_timestamp_ns(
253                value.try_into().unwrap_or_default(),
254            ))
255        }
256    }
257
258    impl Type<Postgres> for Time {
259        fn type_info() -> PgTypeInfo {
260            PgTypeInfo::with_name("TIMESTAMPTZ")
261        }
262        fn compatible(ty: &PgTypeInfo) -> bool {
263            *ty == PgTypeInfo::with_name("TIMESTAMPTZ") || *ty == PgTypeInfo::with_name("TIMESTAMP")
264        }
265    }
266
267    impl Encode<'_, Postgres> for Time {
268        fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> ResultIsNull {
269            let us =
270                i64::try_from(self.timestamp_us()).expect("timestamp too large") - J2000_EPOCH_US;
271            Encode::<Postgres>::encode(us, buf)
272        }
273
274        fn size_hint(&self) -> usize {
275            std::mem::size_of::<i64>()
276        }
277    }
278
279    impl<'r> Decode<'r, Postgres> for Time {
280        fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
281            let us: i64 = Decode::<Postgres>::decode(value)?;
282            Ok(Time::from_timestamp_us(
283                (us + J2000_EPOCH_US).try_into().unwrap_or_default(),
284            ))
285        }
286    }
287}