diesel/expression/
cast.rs

1//! SQL `CAST(expr AS sql_type)` expression support
2
3use crate::expression::{AppearsOnTable, Expression, SelectableExpression, ValidGrouping};
4use crate::query_source::aliasing::{AliasSource, FieldAliasMapper};
5use crate::result::QueryResult;
6use crate::{query_builder, query_source, sql_types};
7
8use std::marker::PhantomData;
9
10pub(crate) mod private {
11    use super::*;
12
13    #[derive(Debug, Clone, Copy, diesel::query_builder::QueryId, sql_types::DieselNumericOps)]
14    pub struct Cast<E, ST> {
15        pub(super) expr: E,
16        pub(super) sql_type: PhantomData<ST>,
17    }
18}
19pub(crate) use private::Cast;
20
21impl<E, ST> Cast<E, ST> {
22    pub(crate) fn new(expr: E) -> Self {
23        Self {
24            expr,
25            sql_type: PhantomData,
26        }
27    }
28}
29
30impl<E, ST, GroupByClause> ValidGrouping<GroupByClause> for Cast<E, ST>
31where
32    E: ValidGrouping<GroupByClause>,
33{
34    type IsAggregate = E::IsAggregate;
35}
36
37impl<E, ST, QS> SelectableExpression<QS> for Cast<E, ST>
38where
39    Cast<E, ST>: AppearsOnTable<QS>,
40    E: SelectableExpression<QS>,
41{
42}
43
44impl<E, ST, QS> AppearsOnTable<QS> for Cast<E, ST>
45where
46    Cast<E, ST>: Expression,
47    E: AppearsOnTable<QS>,
48{
49}
50
51impl<E, ST> Expression for Cast<E, ST>
52where
53    E: Expression,
54    ST: sql_types::SingleValue,
55{
56    type SqlType = ST;
57}
58
59impl<E, ST, DB> query_builder::QueryFragment<DB> for Cast<E, ST>
60where
61    E: query_builder::QueryFragment<DB>,
62    DB: diesel::backend::Backend,
63    ST: KnownCastSqlTypeName<DB>,
64{
65    fn walk_ast<'b>(&'b self, mut out: query_builder::AstPass<'_, 'b, DB>) -> QueryResult<()> {
66        out.push_sql("CAST(");
67        self.expr.walk_ast(out.reborrow())?;
68        out.push_sql(" AS ");
69        out.push_sql(ST::SQL_TYPE_NAME);
70        out.push_sql(")");
71        Ok(())
72    }
73}
74
75/// We know what to write as `sql_type` in the `CAST(expr AS sql_type)` SQL for
76/// `Self`
77///
78/// That is what is returned by `Self::sql_type_name()`
79#[diagnostic::on_unimplemented(
80    note = "In order to use `CAST`, it is necessary that Diesel knows how to express the name \
81		of this type in the given backend.",
82    note = "If you run into this error message and believe that this cast should be supported \
83            open a PR that adds that trait implementation here: https://github.com/diesel-rs/diesel/blob/2fafe60a8f4ca3407dca5fe010a6092fa8a1858a/diesel/src/expression/cast.rs#L113."
84)]
85pub trait KnownCastSqlTypeName<DB> {
86    /// What to write as `sql_type` in the `CAST(expr AS sql_type)` SQL for
87    /// `Self`
88    const SQL_TYPE_NAME: &'static str;
89}
90
91impl<ST, DB> KnownCastSqlTypeName<DB> for sql_types::Nullable<ST>
92where
93    ST: KnownCastSqlTypeName<DB>,
94{
95    const SQL_TYPE_NAME: &'static str = <ST as KnownCastSqlTypeName<DB>>::SQL_TYPE_NAME;
96}
97
98macro_rules! type_name {
99    ($($backend: ty: $backend_feature: literal { $($type: ident => $val: literal,)+ })*) => {
100        $(
101            $(
102				#[cfg(feature = $backend_feature)]
103                impl KnownCastSqlTypeName<$backend> for sql_types::$type {
104                    const SQL_TYPE_NAME: &'static str = $val;
105                }
106            )*
107        )*
108    };
109}
110
111type_name! {
112    diesel::pg::Pg: "postgres_backend" {
113        Bool => "bool",
114        Int2 => "int2",
115        Int4 => "int4",
116        Int8 => "int8",
117        Float => "float4",
118        Double => "float8",
119        Numeric => "numeric",
120        Text => "text",
121        Date => "date",
122        Interval => "interval",
123        Time => "time",
124        Timestamp => "timestamp",
125        Uuid => "uuid",
126        Json => "json",
127        Jsonb => "jsonb",
128    }
129    diesel::mysql::Mysql: "mysql_backend" {
130        Int8 => "signed",
131        Text => "char",
132        Date => "date",
133        Datetime => "datetime",
134        Decimal => "decimal",
135        Time => "time",
136    }
137    diesel::sqlite::Sqlite: "sqlite" {
138        Int4 => "integer",
139        Int8 => "bigint",
140        Text => "text",
141        Json => "json",
142        Jsonb => "jsonb",
143    }
144}
145
146impl<S, E, ST> FieldAliasMapper<S> for Cast<E, ST>
147where
148    S: AliasSource,
149    E: FieldAliasMapper<S>,
150{
151    type Out = Cast<<E as FieldAliasMapper<S>>::Out, ST>;
152
153    fn map(self, alias: &query_source::Alias<S>) -> Self::Out {
154        Cast {
155            expr: self.expr.map(alias),
156            sql_type: self.sql_type,
157        }
158    }
159}
160
161/// Marker trait: this SQL type (`Self`) can be cast to the target SQL type, but some values can be invalid
162pub trait FallibleCastsTo<ST> {}
163
164impl<ST1, ST2> FallibleCastsTo<sql_types::Nullable<ST2>> for sql_types::Nullable<ST1> where
165    ST1: CastsTo<ST2>
166{
167}
168
169/// Marker trait: this SQL type (`Self`) can be cast to the target SQL type
170/// (`ST`) using `CAST(expr AS target_sql_type)`
171pub trait CastsTo<ST>: FallibleCastsTo<ST> {}
172
173impl<ST1, ST2> CastsTo<sql_types::Nullable<ST2>> for sql_types::Nullable<ST1> where ST1: CastsTo<ST2>
174{}
175
176macro_rules! casts_impl {
177    (
178        $(
179            $($feature: literal : )? ($to: tt <- $from: tt),
180        )+
181    ) => {
182        $(
183            $(#[cfg(feature = $feature)])?
184            impl FallibleCastsTo<sql_types::$to> for sql_types::$from {}
185            $(#[cfg(feature = $feature)])?
186            impl CastsTo<sql_types::$to> for sql_types::$from {}
187        )+
188    };
189}
190
191casts_impl!(
192    (Bool <- Int4),
193    (Float4 <- Int4),
194    (Float4 <- Int8),
195    (Float8 <- Float4),
196    (Float8 <- Int4),
197    (Float8 <- Int8),
198    (Int8 <- Int4),
199    (Int8 <- Float4),
200    (Int8 <- Float8),
201    (Int4 <- Bool),
202    (Int4 <- Float4),
203    (Text <- Bool),
204    (Text <- Float4),
205    (Text <- Float8),
206    (Text <- Int4),
207    (Text <- Int8),
208    (Text <- Date),
209    (Text <- Json),
210    (Text <- Jsonb),
211    (Text <- Time),
212    (Json <- Jsonb),
213    (Jsonb <- Json),
214    "mysql_backend": (Text <- Datetime),
215    "postgres_backend": (Text <- Uuid),
216);
217
218macro_rules! fallible_casts_impl {
219    (
220        $(
221            $($feature: literal : )? ($to: tt <- $from: tt),
222        )+
223    ) => {
224        $(
225            $(#[cfg(feature = $feature)])?
226            impl FallibleCastsTo<sql_types::$to> for sql_types::$from {}
227        )+
228    };
229}
230
231fallible_casts_impl!(
232    (Int4 <- Int8),
233    (Int4 <- Float8),
234    (Int4 <- Text),
235    (Int8 <- Text),
236    (Float4 <- Float8),
237    (Float4 <- Text),
238    (Float8 <- Text),
239    (Json <- Text),
240    (Jsonb <- Text),
241    (Bool <- Text),
242    (Date <- Text),
243    (Time <- Text),
244    "mysql_backend": (Datetime <- Text),
245    "postgres_backend": (Uuid <- Text),
246);