diesel/serialize.rs
1//! Types and traits related to serializing values for the database
2
3use std::error::Error;
4use std::fmt;
5use std::io::{self, Write};
6use std::result;
7
8use crate::backend::Backend;
9use crate::query_builder::bind_collector::RawBytesBindCollector;
10use crate::query_builder::BindCollector;
11
12#[doc(inline)]
13#[cfg(feature = "postgres_backend")]
14pub use crate::pg::serialize::WriteTuple;
15
16/// A specialized result type representing the result of serializing
17/// a value for the database.
18pub type Result = result::Result<IsNull, Box<dyn Error + Send + Sync>>;
19
20#[derive(Debug, Copy, Clone, PartialEq, Eq)]
21/// Tiny enum to make the return type of `ToSql` more descriptive
22pub enum IsNull {
23 /// No data was written, as this type is null
24 Yes,
25 /// The value is not null
26 ///
27 /// This does not necessarily mean that any data was written to the buffer.
28 /// For example, an empty string has no data to be sent over the wire, but
29 /// also is not null.
30 No,
31}
32
33/// Wraps a buffer to be written by `ToSql` with additional backend specific
34/// utilities.
35pub struct Output<'a, 'b, DB>
36where
37 DB: Backend,
38 DB::MetadataLookup: 'a,
39{
40 out: <DB::BindCollector<'a> as BindCollector<'a, DB>>::Buffer,
41 metadata_lookup: Option<&'b mut DB::MetadataLookup>,
42}
43
44impl<'a, 'b, DB: Backend> Output<'a, 'b, DB> {
45 /// Construct a new `Output`
46 pub fn new(
47 out: <DB::BindCollector<'a> as BindCollector<'a, DB>>::Buffer,
48 metadata_lookup: &'b mut DB::MetadataLookup,
49 ) -> Self {
50 Output {
51 out,
52 metadata_lookup: Some(metadata_lookup),
53 }
54 }
55
56 /// Consume the current `Output` structure to access the inner buffer type
57 ///
58 /// This function is only useful for people implementing their own Backend.
59 pub fn into_inner(self) -> <DB::BindCollector<'a> as BindCollector<'a, DB>>::Buffer {
60 self.out
61 }
62
63 /// Returns the backend's mechanism for dynamically looking up type
64 /// metadata at runtime, if relevant for the given backend.
65 pub fn metadata_lookup(&mut self) -> &mut DB::MetadataLookup {
66 self.metadata_lookup.as_mut().expect("Lookup is there")
67 }
68
69 /// Set the inner buffer to a specific value
70 ///
71 /// Checkout the documentation of the type of `BindCollector::Buffer`
72 /// for your specific backend for supported types.
73 pub fn set_value<V>(&mut self, value: V)
74 where
75 V: Into<<DB::BindCollector<'a> as BindCollector<'a, DB>>::Buffer>,
76 {
77 self.out = value.into();
78 }
79}
80
81#[cfg(test)]
82impl<'a, DB: Backend> Output<'a, 'static, DB> {
83 /// Returns a `Output` suitable for testing `ToSql` implementations.
84 /// Unsafe to use for testing types which perform dynamic metadata lookup.
85 pub fn test(buffer: <DB::BindCollector<'a> as BindCollector<'a, DB>>::Buffer) -> Self {
86 Self {
87 out: buffer,
88 metadata_lookup: None,
89 }
90 }
91}
92
93impl<DB> Write for Output<'_, '_, DB>
94where
95 for<'c> DB: Backend<BindCollector<'c> = RawBytesBindCollector<DB>>,
96{
97 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
98 self.out.0.write(buf)
99 }
100
101 fn flush(&mut self) -> io::Result<()> {
102 self.out.0.flush()
103 }
104
105 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
106 self.out.0.write_all(buf)
107 }
108
109 fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> {
110 self.out.0.write_fmt(fmt)
111 }
112}
113
114impl<'a, DB> Output<'a, '_, DB>
115where
116 for<'c> DB: Backend<BindCollector<'c> = RawBytesBindCollector<DB>>,
117{
118 /// Call this method whenever you pass an instance of `Output<DB>` by value.
119 ///
120 /// Effectively copies `self`, with a narrower lifetime. When passing a
121 /// reference or a mutable reference, this is normally done by rust
122 /// implicitly. This is why you can pass `&mut Foo` to multiple functions,
123 /// even though mutable references are not `Copy`. However, this is only
124 /// done implicitly for references. For structs with lifetimes it must be
125 /// done explicitly. This method matches the semantics of what Rust would do
126 /// implicitly if you were passing a mutable reference
127 pub fn reborrow<'c>(&'c mut self) -> Output<'c, 'c, DB>
128 where
129 'a: 'c,
130 {
131 Output {
132 out: RawBytesBindCollector::<DB>::reborrow_buffer(&mut self.out),
133 metadata_lookup: match &mut self.metadata_lookup {
134 None => None,
135 Some(m) => Some(&mut **m),
136 },
137 }
138 }
139}
140
141impl<'a, DB> fmt::Debug for Output<'a, '_, DB>
142where
143 <DB::BindCollector<'a> as BindCollector<'a, DB>>::Buffer: fmt::Debug,
144 DB: Backend,
145{
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 self.out.fmt(f)
148 }
149}
150
151/// Serializes a single value to be sent to the database.
152///
153/// The output is sent as a bind parameter, and the data must be written in the
154/// expected format for the given backend.
155///
156/// When possible, implementations of this trait should prefer using an existing
157/// implementation, rather than writing to `out` directly. (For example, if you
158/// are implementing this for an enum, which is represented as an integer in the
159/// database, you should use `i32::to_sql(x, out)` instead of writing to `out`
160/// yourself.)
161///
162/// Any types which implement this trait should also
163/// [`#[derive(AsExpression)]`](derive@crate::expression::AsExpression).
164///
165/// ### Backend specific details
166///
167/// - For PostgreSQL, the bytes will be sent using the binary protocol, not text.
168/// - For SQLite, all implementations should be written in terms of an existing
169/// `ToSql` implementation.
170/// - For MySQL, the expected bytes will depend on the return value of
171/// `type_metadata` for the given SQL type. See [`MysqlType`] for details.
172/// - For third party backends, consult that backend's documentation.
173///
174/// [`MysqlType`]: ../mysql/enum.MysqlType.html
175///
176/// ### Examples
177///
178/// Most implementations of this trait will be defined in terms of an existing
179/// implementation.
180///
181/// ```rust
182/// # use diesel::backend::Backend;
183/// # use diesel::expression::AsExpression;
184/// # use diesel::sql_types::*;
185/// # use diesel::serialize::{self, ToSql, Output};
186/// # use std::io::Write;
187/// #
188/// #[repr(i32)]
189/// #[derive(Debug, Clone, Copy, AsExpression)]
190/// #[diesel(sql_type = Integer)]
191/// pub enum MyEnum {
192/// A = 1,
193/// B = 2,
194/// }
195///
196/// impl<DB> ToSql<Integer, DB> for MyEnum
197/// where
198/// DB: Backend,
199/// i32: ToSql<Integer, DB>,
200/// {
201/// fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result {
202/// match self {
203/// MyEnum::A => 1.to_sql(out),
204/// MyEnum::B => 2.to_sql(out),
205/// }
206/// }
207/// }
208/// ```
209///
210/// Example of creating a custom type mapping based on a MySQL [enum type](https://dev.mysql.com/doc/refman/8.0/en/enum.html)
211///
212/// This is designed to reuse the SQL type definition generated by diesel-cli
213///
214/// ```rust
215/// # use diesel::backend::Backend;
216/// # use diesel::expression::AsExpression;
217/// # use diesel::sql_types::*;
218/// # use diesel::serialize::{self, ToSql, Output, IsNull};
219/// # use std::io::Write;
220/// #
221/// pub mod sql_types {
222/// #[derive(diesel::sql_types::SqlType)]
223/// #[diesel(mysql_type(name = "Enum"))]
224/// pub struct PostEnum; //<- generated by diesel cli
225/// }
226/// #[derive(Debug, AsExpression, PartialEq, Clone)]
227/// #[diesel(sql_type = sql_types::PostEnum)]
228/// pub enum Post {
229/// FirstValue,
230/// SecondValue,
231/// }
232///
233/// # #[cfg(feature = "mysql")]
234/// impl ToSql<sql_types::PostEnum, diesel::mysql::Mysql> for Post {
235/// fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, diesel::mysql::Mysql>) -> serialize::Result {
236/// match *self {
237/// // these string values need to match the labels used in your
238/// // enum definition in SQL. So this expects that you defined the
239/// /// relevant enum type as`ENUM('one', 'two')` in your `CREATE TABLE` statement
240/// Post::FirstValue => out.write_all(b"one")?,
241/// Post::SecondValue => out.write_all(b"two")?,
242/// }
243/// Ok(IsNull::No)
244/// }
245/// }
246/// ```
247///
248/// Using temporary values as part of the `ToSql` implementation requires additional
249/// work.
250///
251/// Backends using [`RawBytesBindCollector`] as [`BindCollector`] copy the serialized values as part
252/// of `Write` implementation. This includes the `Mysql` and the `Pg` backend provided by diesel.
253/// This means existing `ToSql` implementations can be used even with
254/// temporary values. For these it is required to call
255/// [`Output::reborrow`] to shorten the lifetime of the `Output` type correspondingly.
256///
257/// ```
258/// # use diesel::backend::Backend;
259/// # use diesel::expression::AsExpression;
260/// # use diesel::sql_types::*;
261/// # use diesel::serialize::{self, ToSql, Output};
262/// # use std::io::Write;
263/// #
264/// #[repr(i32)]
265/// #[derive(Debug, Clone, Copy, AsExpression)]
266/// #[diesel(sql_type = Integer)]
267/// pub enum MyEnum {
268/// A = 1,
269/// B = 2,
270/// }
271///
272/// # #[cfg(feature = "postgres")]
273/// impl ToSql<Integer, diesel::pg::Pg> for MyEnum
274/// where
275/// i32: ToSql<Integer, diesel::pg::Pg>,
276/// {
277/// fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, diesel::pg::Pg>) -> serialize::Result {
278/// let v = *self as i32;
279/// <i32 as ToSql<Integer, diesel::pg::Pg>>::to_sql(&v, &mut out.reborrow())
280/// }
281/// }
282/// ````
283///
284/// For any other backend the [`Output::set_value`] method provides a way to
285/// set the output value directly. Checkout the documentation of the corresponding
286/// `BindCollector::Buffer` type for provided `From<T>` implementations for a list
287/// of accepted types. For the `Sqlite` backend see `SqliteBindValue`.
288///
289/// ```
290/// # use diesel::backend::Backend;
291/// # use diesel::expression::AsExpression;
292/// # use diesel::sql_types::*;
293/// # use diesel::serialize::{self, ToSql, Output, IsNull};
294/// # use std::io::Write;
295/// #
296/// #[repr(i32)]
297/// #[derive(Debug, Clone, Copy, AsExpression)]
298/// #[diesel(sql_type = Integer)]
299/// pub enum MyEnum {
300/// A = 1,
301/// B = 2,
302/// }
303///
304/// # #[cfg(feature = "sqlite")]
305/// impl ToSql<Integer, diesel::sqlite::Sqlite> for MyEnum
306/// where
307/// i32: ToSql<Integer, diesel::sqlite::Sqlite>,
308/// {
309/// fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, diesel::sqlite::Sqlite>) -> serialize::Result {
310/// out.set_value(*self as i32);
311/// Ok(IsNull::No)
312/// }
313/// }
314/// ````
315pub trait ToSql<A, DB: Backend>: fmt::Debug {
316 /// See the trait documentation.
317 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> Result;
318}
319
320impl<A, T, DB> ToSql<A, DB> for &T
321where
322 DB: Backend,
323 T: ToSql<A, DB> + ?Sized,
324{
325 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> Result {
326 (*self).to_sql(out)
327 }
328}