diesel/sqlite/expression/expression_methods.rs
1//! Sqlite specific expression methods.
2
3pub(in crate::sqlite) use self::private::{
4 BinaryOrNullableBinary, JsonOrNullableJson, MaybeNullableValue, NotBlob, TextOrNullableText,
5 TextOrNullableTextOrBinaryOrNullableBinary,
6};
7use super::operators::*;
8use crate::dsl;
9use crate::expression::grouped::Grouped;
10use crate::expression::{AsExpression, Expression};
11use crate::expression_methods::json_expression_methods::private::JsonOrNullableJsonOrJsonbOrNullableJsonb;
12use crate::expression_methods::json_expression_methods::{AnyJsonExpressionMethods, JsonIndex};
13use crate::sql_types::SqlType;
14
15/// Sqlite specific methods which are present on all expressions.
16#[cfg(feature = "sqlite")]
17pub trait SqliteExpressionMethods: Expression + Sized {
18 /// Creates a Sqlite `IS` expression.
19 ///
20 /// The `IS` operator work like = except when one or both of the operands are NULL.
21 /// In this case, if both operands are NULL, then the `IS` operator evaluates to true.
22 /// If one operand is NULL and the other is not, then the `IS` operator evaluates to false.
23 /// It is not possible for an `IS` expression to evaluate to NULL.
24 ///
25 /// # Example
26 ///
27 /// ```rust
28 /// # include!("../../doctest_setup.rs");
29 /// #
30 /// # fn main() {
31 /// # run_test().unwrap();
32 /// # }
33 /// #
34 /// # fn run_test() -> QueryResult<()> {
35 /// # use schema::animals::dsl::*;
36 /// # let connection = &mut establish_connection();
37 /// let jack_is_a_dog = animals
38 /// .select(name)
39 /// .filter(species.is("dog"))
40 /// .get_results::<Option<String>>(connection)?;
41 /// assert_eq!(vec![Some("Jack".to_string())], jack_is_a_dog);
42 /// # Ok(())
43 /// # }
44 /// ```
45 fn is<T>(self, other: T) -> dsl::Is<Self, T>
46 where
47 Self::SqlType: SqlType,
48 T: AsExpression<Self::SqlType>,
49 {
50 Grouped(Is::new(self, other.as_expression()))
51 }
52
53 /// Creates a Sqlite `IS NOT` expression.
54 ///
55 /// The `IS NOT` operator work like != except when one or both of the operands are NULL.
56 /// In this case, if both operands are NULL, then the `IS NOT` operator evaluates to false.
57 /// If one operand is NULL and the other is not, then the `IS NOT` operator is true.
58 /// It is not possible for an `IS NOT` expression to evaluate to NULL.
59 ///
60 /// # Example
61 ///
62 /// ```rust
63 /// # include!("../../doctest_setup.rs");
64 /// #
65 /// # fn main() {
66 /// # run_test().unwrap();
67 /// # }
68 /// #
69 /// # fn run_test() -> QueryResult<()> {
70 /// # use schema::animals::dsl::*;
71 /// # let connection = &mut establish_connection();
72 /// let jack_is_not_a_spider = animals
73 /// .select(name)
74 /// .filter(species.is_not("spider"))
75 /// .get_results::<Option<String>>(connection)?;
76 /// assert_eq!(vec![Some("Jack".to_string())], jack_is_not_a_spider);
77 /// # Ok(())
78 /// # }
79 /// ```
80 #[allow(clippy::wrong_self_convention)] // This is named after the sql operator
81 fn is_not<T>(self, other: T) -> dsl::IsNot<Self, T>
82 where
83 Self::SqlType: SqlType,
84 T: AsExpression<Self::SqlType>,
85 {
86 Grouped(IsNot::new(self, other.as_expression()))
87 }
88}
89
90impl<T: Expression> SqliteExpressionMethods for T {}
91
92/// SQLite specific methods present on JSON and JSONB expressions.
93#[cfg(feature = "sqlite")]
94pub trait SqliteAnyJsonExpressionMethods: AnyJsonExpressionMethods + Expression + Sized {
95 /// Creates a SQLite `->` expression.
96 ///
97 /// This operator extracts the value associated with the given path or key from a JSON value.
98 /// The right-hand side can be:
99 /// - A string path expression (e.g., `"$.key"`, `"$.c"`, or `"c"` which is interpreted as `"$.c"`)
100 /// - An integer for array indexing (e.g., `0` for the first element, or `-1` for the last element on SQLite 3.47+)
101 ///
102 /// **Always returns a TEXT JSON representation** (SQL type `Json`), even when the input is JSONB.
103 /// To get JSONB output, use `jsonb_extract()` function instead.
104 ///
105 /// # Example
106 ///
107 /// ```rust
108 /// # include!("../../doctest_setup.rs");
109 /// #
110 /// # table! {
111 /// # contacts {
112 /// # id -> Integer,
113 /// # name -> Text,
114 /// # address -> Json,
115 /// # }
116 /// # }
117 /// #
118 /// # fn main() {
119 /// # #[cfg(feature = "serde_json")]
120 /// # run_test().unwrap();
121 /// # }
122 /// #
123 /// # #[cfg(feature = "serde_json")]
124 /// # fn run_test() -> QueryResult<()> {
125 /// # use self::contacts::dsl::*;
126 /// # use diesel::dsl::sql;
127 /// # use diesel::sql_types::Json;
128 /// # let conn = &mut establish_connection();
129 /// # diesel::sql_query("DROP TABLE IF EXISTS contacts").execute(conn).unwrap();
130 /// # diesel::sql_query("CREATE TABLE contacts (
131 /// # id INTEGER PRIMARY KEY,
132 /// # name TEXT NOT NULL,
133 /// # address TEXT NOT NULL
134 /// # )").execute(conn).unwrap();
135 /// #
136 /// let json_value = serde_json::json!({
137 /// "street": "Article Circle Expressway 1",
138 /// "city": "North Pole",
139 /// "postcode": "99705",
140 /// "state": "Alaska"
141 /// });
142 ///
143 /// let result = diesel::select(sql::<Json>(r#"json('{"a": {"b": [1, 2, 3]}}')"#)
144 /// .retrieve_as_object_sqlite("$.a.b[0]"))
145 /// .get_result::<serde_json::Value>(conn)?;
146 /// assert_eq!(serde_json::json!(1), result);
147 ///
148 /// let result = diesel::select(sql::<Jsonb>(r#"json('{"a": [1, 2, 3]}')"#)
149 /// .retrieve_as_object_sqlite("$.a[1]"))
150 /// .get_result::<serde_json::Value>(conn)?;
151 /// assert_eq!(serde_json::json!(2), result);
152 ///
153 /// # Ok(())
154 /// # }
155 /// ```
156 fn retrieve_as_object_sqlite<T>(
157 self,
158 other: T,
159 ) -> crate::sqlite::expression::helper_types::RetrieveAsObjectSqlite<Self, T>
160 where
161 T: JsonIndex,
162 <T::Expression as Expression>::SqlType: SqlType,
163 {
164 Grouped(RetrieveAsObjectSqlite::new(
165 self,
166 other.into_json_index_expression(),
167 ))
168 }
169}
170
171#[doc(hidden)]
172impl<T> SqliteAnyJsonExpressionMethods for T
173where
174 T: Expression,
175 T::SqlType: JsonOrNullableJsonOrJsonbOrNullableJsonb,
176{
177}
178
179pub(in crate::sqlite) mod private {
180 use crate::sql_types::{
181 BigInt, Binary, Bool, Date, Double, Float, Integer, Json, MaybeNullableType, Nullable,
182 Numeric, SingleValue, SmallInt, SqlType, Text, Time, Timestamp, TimestamptzSqlite,
183 };
184
185 #[diagnostic::on_unimplemented(
186 message = "`{Self}` is neither `diesel::sql_types::Text` nor `diesel::sql_types::Nullable<Text>`",
187 note = "try to provide an expression that produces one of the expected sql types"
188 )]
189 pub trait TextOrNullableText {}
190
191 impl TextOrNullableText for Text {}
192 impl TextOrNullableText for Nullable<Text> {}
193
194 #[diagnostic::on_unimplemented(
195 message = "`{Self}` is neither `diesel::sql_types::Binary` nor `diesel::sql_types::Nullable<Binary>`",
196 note = "try to provide an expression that produces one of the expected sql types"
197 )]
198 pub trait BinaryOrNullableBinary {}
199
200 impl BinaryOrNullableBinary for Binary {}
201 impl BinaryOrNullableBinary for Nullable<Binary> {}
202
203 #[diagnostic::on_unimplemented(
204 message = "`{Self}` is neither `diesel::sql_types::Text`, `diesel::sql_types::Nullable<Text>`, `diesel::sql_types::Binary` nor `diesel::sql_types::Nullable<Binary>`",
205 note = "try to provide an expression that produces one of the expected sql types"
206 )]
207 pub trait TextOrNullableTextOrBinaryOrNullableBinary {}
208
209 impl TextOrNullableTextOrBinaryOrNullableBinary for Text {}
210 impl TextOrNullableTextOrBinaryOrNullableBinary for Nullable<Text> {}
211 impl TextOrNullableTextOrBinaryOrNullableBinary for Binary {}
212 impl TextOrNullableTextOrBinaryOrNullableBinary for Nullable<Binary> {}
213
214 #[diagnostic::on_unimplemented(
215 message = "`{Self}` is neither `diesel::sql_types::Json` nor `diesel::sql_types::Nullable<Json>`",
216 note = "try to provide an expression that produces one of the expected sql types"
217 )]
218 pub trait JsonOrNullableJson {}
219 impl JsonOrNullableJson for Json {}
220 impl JsonOrNullableJson for Nullable<Json> {}
221
222 pub trait MaybeNullableValue<T>: SingleValue {
223 type Out: SingleValue;
224 }
225
226 impl<T, O> MaybeNullableValue<O> for T
227 where
228 T: SingleValue,
229 T::IsNull: MaybeNullableType<O>,
230 <T::IsNull as MaybeNullableType<O>>::Out: SingleValue,
231 {
232 type Out = <T::IsNull as MaybeNullableType<O>>::Out;
233 }
234
235 #[diagnostic::on_unimplemented(
236 message = "`{Self}` is neither any of `diesel::sql_types::{{
237 Text, Float, Double, Numeric, Bool, Integer, SmallInt, BigInt,
238 Date, Time, Timestamp, TimestamptzSqlite, Json
239 }}` nor `diesel::sql_types::Nullable<Any of the above>`",
240 note = "try to provide an expression that produces one of the expected sql types"
241 )]
242 pub trait NotBlob: SqlType + SingleValue {}
243
244 impl<T> NotBlob for Nullable<T> where T: NotBlob {}
245 impl NotBlob for Text {}
246 impl NotBlob for Float {}
247 impl NotBlob for Double {}
248 impl NotBlob for Numeric {}
249 impl NotBlob for Bool {}
250 impl NotBlob for Integer {}
251 impl NotBlob for SmallInt {}
252 impl NotBlob for BigInt {}
253 impl NotBlob for Date {}
254 impl NotBlob for Time {}
255 impl NotBlob for Timestamp {}
256 impl NotBlob for TimestamptzSqlite {}
257 impl NotBlob for Json {}
258}