drizzle_mysql/traits/value.rs
1//! [`DrizzleMySQLColumn`], for custom column types, and the JSON projection
2//! metadata of the built-in SQL type markers.
3
4use crate::values::{MySQLValue, OwnedMySQLValue};
5use drizzle_core::error::DrizzleError;
6
7/// A Rust type that can be stored in a `MySQL` column.
8///
9/// Implement it to use your own type as a field of a `#[MySQLTable]`.
10/// [`SQLType`](Self::SQLType) sets which expressions the column can be used
11/// in, [`SQL_TYPE`](Self::SQL_TYPE) is written into the DDL, and
12/// [`decode`](Self::decode) / [`encode`](Self::encode) convert values.
13/// Implementing it also gives `From<T> for MySQLValue`, which goes through
14/// [`encode_owned`](Self::encode_owned).
15///
16/// # Examples
17///
18/// A `u32` stored as four big-endian bytes:
19///
20/// ```
21/// use drizzle_core::error::DrizzleError;
22/// use drizzle_mysql::traits::DrizzleMySQLColumn;
23/// use drizzle_mysql::types::Binary;
24/// use drizzle_mysql::values::MySQLValue;
25/// use std::borrow::Cow;
26///
27/// struct U32Be(u32);
28///
29/// impl DrizzleMySQLColumn for U32Be {
30/// type SQLType = Binary;
31/// const SQL_TYPE: &'static str = "BINARY(4)";
32///
33/// fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError> {
34/// let MySQLValue::Bytes(bytes) = value else {
35/// return Err(DrizzleError::ConversionError("expected bytes".into()));
36/// };
37/// let bytes: [u8; 4] = bytes
38/// .as_ref()
39/// .try_into()
40/// .map_err(|_| DrizzleError::ConversionError("expected 4 bytes".into()))?;
41/// Ok(Self(u32::from_be_bytes(bytes)))
42/// }
43///
44/// fn encode(&self) -> MySQLValue<'_> {
45/// MySQLValue::Bytes(Cow::Owned(self.0.to_be_bytes().to_vec()))
46/// }
47/// }
48///
49/// let value = MySQLValue::from(U32Be(7));
50/// assert_eq!(value, MySQLValue::Bytes(Cow::Borrowed(&[0, 0, 0, 7][..])));
51/// assert_eq!(U32Be::decode(value).unwrap().0, 7);
52/// assert!(U32Be::decode(MySQLValue::Int(7)).is_err());
53/// ```
54#[diagnostic::on_unimplemented(
55 message = "`{Self}` cannot be used as a MySQL column type",
56 note = "add #[derive(MySQLEnum)] for enum types, or implement DrizzleMySQLColumn"
57)]
58pub trait DrizzleMySQLColumn: Sized {
59 /// The SQL type marker of the column, one of the markers in
60 /// [`crate::types`].
61 type SQLType: MySQLColumnType;
62
63 /// The column type written into the DDL, such as `BINARY(4)`, `TEXT` or
64 /// `BIGINT UNSIGNED`.
65 const SQL_TYPE: &'static str;
66
67 /// Decodes a value read from the database.
68 ///
69 /// # Errors
70 ///
71 /// Returns [`DrizzleError::ConversionError`] when `value` does not match
72 /// this column's storage representation.
73 fn decode(value: MySQLValue<'_>) -> Result<Self, DrizzleError>;
74
75 /// Encodes the value as a bind parameter, borrowing from `self` where
76 /// possible.
77 fn encode(&self) -> MySQLValue<'_>;
78
79 /// Encodes the value as an owned bind parameter.
80 ///
81 /// The default calls [`encode`](Self::encode) and copies the result.
82 /// Override it when the type can move its buffer into the value instead.
83 fn encode_owned(self) -> OwnedMySQLValue {
84 self.encode().into_owned()
85 }
86
87 /// Decodes a cell from the JSON produced by the relational query API
88 /// (`query` feature).
89 ///
90 /// The default converts the JSON to the value the driver would return
91 /// for [`SQLType`](Self::SQLType) and calls [`decode`](Self::decode).
92 /// Override it only when the JSON form differs from that.
93 ///
94 /// # Errors
95 ///
96 /// Returns [`DrizzleError::ConversionError`] if the JSON does not match
97 /// the column's type or [`decode`](Self::decode) rejects it.
98 #[cfg(feature = "query")]
99 fn decode_json(value: &serde_json::Value) -> Result<Self, DrizzleError>
100 where
101 Self::SQLType: MySQLColumnType,
102 {
103 let value = crate::driver::projected_value(
104 value,
105 <Self::SQLType as MySQLColumnType>::JSON_STORAGE,
106 )?;
107 Self::decode(value.into())
108 }
109}
110
111/// MySQL JSON representation used for a typed relational projection.
112#[cfg(feature = "query")]
113#[doc(hidden)]
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum MySQLJsonStorage {
116 Signed,
117 Unsigned,
118 Double,
119 Boolean,
120 Text,
121 SignedText,
122 UnsignedText,
123 FloatText,
124 Binary,
125 Json,
126 Date,
127 Time,
128 DateTime,
129}
130
131/// How the relational query API projects a built-in `MySQL` SQL type marker
132/// to JSON. Sealed.
133#[doc(hidden)]
134pub trait MySQLColumnType: drizzle_core::types::DataType + private::Sealed {
135 #[cfg(feature = "query")]
136 const JSON_PROJECTION: drizzle_core::query::JsonProjectionKind;
137 #[cfg(feature = "query")]
138 const JSON_STORAGE: MySQLJsonStorage;
139}
140
141mod private {
142 pub trait Sealed {}
143}
144
145macro_rules! mysql_column_types {
146 ($($type:ty => ($projection:ident, $storage:ident)),+ $(,)?) => {
147 $(
148 impl private::Sealed for $type {}
149 impl MySQLColumnType for $type {
150 #[cfg(feature = "query")]
151 const JSON_PROJECTION: drizzle_core::query::JsonProjectionKind =
152 drizzle_core::query::JsonProjectionKind::$projection;
153 #[cfg(feature = "query")]
154 const JSON_STORAGE: MySQLJsonStorage = MySQLJsonStorage::$storage;
155 }
156 )+
157 };
158}
159
160mysql_column_types! {
161 crate::types::TinyInt => (Native, Signed),
162 crate::types::SmallInt => (Native, Signed),
163 crate::types::MediumInt => (Native, Signed),
164 crate::types::Int => (Native, Signed),
165 crate::types::TinyIntUnsigned => (Native, Unsigned),
166 crate::types::SmallIntUnsigned => (Native, Unsigned),
167 crate::types::MediumIntUnsigned => (Native, Unsigned),
168 crate::types::IntUnsigned => (Native, Unsigned),
169 crate::types::BigInt => (Text, SignedText),
170 crate::types::BigIntUnsigned => (Text, UnsignedText),
171 crate::types::Float => (Text, FloatText),
172 crate::types::Double => (Native, Double),
173 crate::types::Decimal => (Text, Text),
174 crate::types::Boolean => (Native, Boolean),
175 crate::types::Char => (Native, Text),
176 crate::types::Varchar => (Native, Text),
177 crate::types::TinyText => (Native, Text),
178 crate::types::Text => (Native, Text),
179 crate::types::MediumText => (Native, Text),
180 crate::types::LongText => (Native, Text),
181 crate::types::Binary => (TaggedHex, Binary),
182 crate::types::Varbinary => (TaggedHex, Binary),
183 crate::types::TinyBlob => (TaggedHex, Binary),
184 crate::types::Blob => (TaggedHex, Binary),
185 crate::types::MediumBlob => (TaggedHex, Binary),
186 crate::types::LongBlob => (TaggedHex, Binary),
187 crate::types::Json => (Native, Json),
188 crate::types::Date => (Text, Date),
189 crate::types::Time => (Text, Time),
190 crate::types::DateTime => (Text, DateTime),
191 crate::types::Timestamp => (Text, DateTime),
192 crate::types::Year => (Native, Unsigned),
193 crate::types::Enum => (Native, Text),
194 crate::types::Set => (Native, Text),
195 crate::types::Bit => (Unsigned, Unsigned),
196 crate::types::Any => (Native, Json),
197}