drizzle_postgres/expr/array_ops.rs
1//! `PostgreSQL` array operators. Documented in [`crate::expr`].
2
3#[cfg(not(feature = "std"))]
4use crate::prelude::*;
5use crate::values::PostgresValue;
6use drizzle_core::ToSQL;
7use drizzle_core::expr::{AggregateKind, Expr, ExprSources, NonNull, SQLExpr, Scalar};
8use drizzle_core::scope::Arg;
9use drizzle_core::sql::{SQL, SQLChunk};
10use drizzle_types::postgres::types::{Any, Boolean};
11use drizzle_types::{Array, Compatible, DataType, Placeholder};
12
13/// SQL type pairs that the array operators (`@>`, `<@`, `&&`) accept.
14///
15/// `Self` is the SQL type of the left operand and `Rhs` the SQL type of the
16/// right operand. The pair is accepted when:
17///
18/// - both are arrays, `Array<T>` and `Array<U>`, and `T` is [`Compatible`]
19/// with `U` (so `int4[]` works with `int8[]`, but not with `text[]`);
20/// - the left side is an array and the right side is a [`Placeholder`] or
21/// untyped SQL ([`Any`]);
22/// - the left side is untyped SQL ([`Any`]), which accepts any right side.
23///
24/// Anything else, such as a single value or a plain `Vec`, is rejected at
25/// compile time. Wrap a Rust list in [`PgArray`] to bind it as one array.
26///
27/// # Type safety
28///
29/// ```compile_fail
30/// use drizzle_core::expr::raw_non_null;
31/// use drizzle_postgres::expr::array_contains;
32/// use drizzle_postgres::values::PostgresValue;
33/// use drizzle_types::{Array, postgres::types::Text};
34///
35/// let tags = raw_non_null::<PostgresValue, Array<Text>>("tags");
36/// // A single value is not an array: wrap it as `PgArray(vec!["rust"])`.
37/// let _ = array_contains(tags, "rust");
38/// ```
39///
40/// ```compile_fail
41/// use drizzle_core::expr::raw_non_null;
42/// use drizzle_postgres::expr::{array_overlaps, PgArray};
43/// use drizzle_postgres::values::PostgresValue;
44/// use drizzle_types::{Array, postgres::types::Int4};
45///
46/// let ids = raw_non_null::<PostgresValue, Array<Int4>>("ids");
47/// // `int4[]` cannot be compared with `text[]`.
48/// let _ = array_overlaps(ids, PgArray(vec!["1"]));
49/// ```
50#[diagnostic::on_unimplemented(
51 message = "PostgreSQL array operators cannot combine `{Self}` with `{Rhs}`",
52 label = "both operands must be arrays with compatible element types",
53 note = "pass a bound array with `PgArray(vec![...])`; a bare value is not an array"
54)]
55pub trait ArrayOperand<Rhs> {}
56
57impl<T: DataType, U: DataType> ArrayOperand<Array<U>> for Array<T> where T: Compatible<U> {}
58impl<T: DataType> ArrayOperand<Any> for Array<T> {}
59impl<T: DataType> ArrayOperand<Placeholder> for Array<T> {}
60impl<R> ArrayOperand<R> for Any {}
61
62/// Binds a `Vec<T>` as a single `PostgreSQL` array parameter.
63///
64/// A plain `Vec<T>` renders one parameter per element (`$1, $2, $3`), which
65/// suits `IN (...)` lists. The array operators need one array value instead,
66/// so wrap the list: `PgArray(vec![1, 2])` renders as a single `$1`.
67///
68/// Its SQL type is `Array<T's SQL type>`, so `PgArray(vec!["a"])` is an
69/// `Array<Text>` and `PgArray(vec![1_i32])` an `Array<Int4>`. It is never NULL.
70///
71/// # Examples
72///
73/// ```
74/// use drizzle_core::{ToSQL, expr::raw_non_null};
75/// use drizzle_postgres::expr::{array_contains, PgArray};
76/// use drizzle_postgres::values::PostgresValue;
77/// use drizzle_types::{Array, postgres::types::Text};
78///
79/// let tags = raw_non_null::<PostgresValue, Array<Text>>("tags");
80/// let condition = array_contains(tags, PgArray(vec!["rust", "python"]));
81/// let sql = condition.to_sql();
82/// assert_eq!(sql.sql(), "tags @> $1");
83/// assert_eq!(sql.params().count(), 1); // one array parameter
84/// ```
85pub struct PgArray<T>(pub Vec<T>);
86
87impl<'a, T> ToSQL<'a, PostgresValue<'a>> for PgArray<T>
88where
89 T: Into<PostgresValue<'a>> + Clone,
90{
91 fn to_sql(&self) -> SQL<'a, PostgresValue<'a>> {
92 let array: Vec<PostgresValue<'a>> = self.0.iter().map(|v| v.clone().into()).collect();
93 SQL::param(PostgresValue::Array(array))
94 }
95}
96
97impl<T> ExprSources for PgArray<T> {
98 type Sources = ();
99}
100
101impl<'a, T> Expr<'a, PostgresValue<'a>> for PgArray<T>
102where
103 T: Expr<'a, PostgresValue<'a>> + Into<PostgresValue<'a>> + Clone,
104{
105 type SQLType = Array<T::SQLType>;
106 type Nullable = NonNull;
107 type Aggregate = Scalar;
108}
109
110/// Tests whether the left array contains every element of the right array (`@>`).
111///
112/// Both operands must be arrays with compatible element types; see
113/// [`ArrayOperand`]. The result is `boolean`, and NULL when either operand is NULL.
114///
115/// # Examples
116///
117/// ```
118/// use drizzle_core::{ToSQL, expr::raw_non_null};
119/// use drizzle_postgres::expr::{array_contains, PgArray};
120/// use drizzle_postgres::values::PostgresValue;
121/// use drizzle_types::{Array, postgres::types::Text};
122///
123/// let tags = raw_non_null::<PostgresValue, Array<Text>>("tags");
124/// // Rows tagged with both "rust" and "sql" (and possibly more).
125/// let condition = array_contains(tags, PgArray(vec!["rust", "sql"]));
126/// assert_eq!(condition.to_sql().sql(), "tags @> $1");
127/// ```
128#[allow(clippy::type_complexity)]
129pub fn array_contains<'a, L, R>(
130 left: L,
131 right: R,
132) -> SQLExpr<
133 'a,
134 PostgresValue<'a>,
135 Boolean,
136 NonNull,
137 <L::Aggregate as AggregateKind>::Or<R::Aggregate>,
138 (Arg<L::Nullable, L::Sources>, Arg<R::Nullable, R::Sources>),
139>
140where
141 L: Expr<'a, PostgresValue<'a>>,
142 R: Expr<'a, PostgresValue<'a>>,
143 L::SQLType: ArrayOperand<R::SQLType>,
144{
145 SQLExpr::new(
146 left.to_sql()
147 .push(SQLChunk::Raw("@>".into()))
148 .append(right.to_sql()),
149 )
150}
151
152/// Tests whether every element of the left array is in the right array (`<@`).
153///
154/// Both operands must be arrays with compatible element types; see
155/// [`ArrayOperand`]. The result is `boolean`, and NULL when either operand is NULL.
156///
157/// # Examples
158///
159/// ```
160/// use drizzle_core::{ToSQL, expr::raw_non_null};
161/// use drizzle_postgres::expr::{array_contained, PgArray};
162/// use drizzle_postgres::values::PostgresValue;
163/// use drizzle_types::{Array, postgres::types::Text};
164///
165/// let tags = raw_non_null::<PostgresValue, Array<Text>>("tags");
166/// // Rows whose tags all come from the allowed list.
167/// let condition = array_contained(tags, PgArray(vec!["rust", "sql", "web"]));
168/// assert_eq!(condition.to_sql().sql(), "tags <@ $1");
169/// ```
170#[allow(clippy::type_complexity)]
171pub fn array_contained<'a, L, R>(
172 left: L,
173 right: R,
174) -> SQLExpr<
175 'a,
176 PostgresValue<'a>,
177 Boolean,
178 NonNull,
179 <L::Aggregate as AggregateKind>::Or<R::Aggregate>,
180 (Arg<L::Nullable, L::Sources>, Arg<R::Nullable, R::Sources>),
181>
182where
183 L: Expr<'a, PostgresValue<'a>>,
184 R: Expr<'a, PostgresValue<'a>>,
185 L::SQLType: ArrayOperand<R::SQLType>,
186{
187 SQLExpr::new(
188 left.to_sql()
189 .push(SQLChunk::Raw("<@".into()))
190 .append(right.to_sql()),
191 )
192}
193
194/// Tests whether two arrays share at least one element (`&&`).
195///
196/// Both operands must be arrays with compatible element types; see
197/// [`ArrayOperand`]. The result is `boolean`, and NULL when either operand is NULL.
198///
199/// # Examples
200///
201/// ```
202/// use drizzle_core::{ToSQL, expr::raw_non_null};
203/// use drizzle_postgres::expr::{array_overlaps, PgArray};
204/// use drizzle_postgres::values::PostgresValue;
205/// use drizzle_types::{Array, postgres::types::Int4};
206///
207/// let team_ids = raw_non_null::<PostgresValue, Array<Int4>>("team_ids");
208/// // Rows in team 1 or team 2.
209/// let condition = array_overlaps(team_ids, PgArray(vec![1_i32, 2]));
210/// assert_eq!(condition.to_sql().sql(), "team_ids && $1");
211/// ```
212#[allow(clippy::type_complexity)]
213pub fn array_overlaps<'a, L, R>(
214 left: L,
215 right: R,
216) -> SQLExpr<
217 'a,
218 PostgresValue<'a>,
219 Boolean,
220 NonNull,
221 <L::Aggregate as AggregateKind>::Or<R::Aggregate>,
222 (Arg<L::Nullable, L::Sources>, Arg<R::Nullable, R::Sources>),
223>
224where
225 L: Expr<'a, PostgresValue<'a>>,
226 R: Expr<'a, PostgresValue<'a>>,
227 L::SQLType: ArrayOperand<R::SQLType>,
228{
229 SQLExpr::new(
230 left.to_sql()
231 .push(SQLChunk::Raw("&&".into()))
232 .append(right.to_sql()),
233 )
234}
235
236/// Method forms of the array operators, available on every `PostgreSQL` expression.
237///
238/// Each method calls the free function of the same name and has the same
239/// operand rules ([`ArrayOperand`]).
240///
241/// # Examples
242///
243/// ```
244/// use drizzle_core::{ToSQL, expr::raw_non_null};
245/// use drizzle_postgres::expr::{ArrayExprExt, PgArray};
246/// use drizzle_postgres::values::PostgresValue;
247/// use drizzle_types::{Array, postgres::types::Text};
248///
249/// let tags = raw_non_null::<PostgresValue, Array<Text>>("tags");
250/// let condition = tags.array_overlaps(PgArray(vec!["rust"]));
251/// assert_eq!(condition.to_sql().sql(), "tags && $1");
252/// ```
253pub trait ArrayExprExt<'a>: Expr<'a, PostgresValue<'a>> + Sized {
254 /// Tests whether `self` contains every element of `other` (`@>`).
255 ///
256 /// See [`array_contains`].
257 #[allow(clippy::type_complexity)]
258 fn array_contains<R>(
259 self,
260 other: R,
261 ) -> SQLExpr<
262 'a,
263 PostgresValue<'a>,
264 Boolean,
265 NonNull,
266 <Self::Aggregate as AggregateKind>::Or<R::Aggregate>,
267 (
268 Arg<Self::Nullable, Self::Sources>,
269 Arg<R::Nullable, R::Sources>,
270 ),
271 >
272 where
273 R: Expr<'a, PostgresValue<'a>>,
274 Self::SQLType: ArrayOperand<R::SQLType>,
275 {
276 array_contains(self, other)
277 }
278
279 /// Tests whether every element of `self` is in `other` (`<@`).
280 ///
281 /// See [`array_contained`].
282 #[allow(clippy::type_complexity)]
283 fn array_contained<R>(
284 self,
285 other: R,
286 ) -> SQLExpr<
287 'a,
288 PostgresValue<'a>,
289 Boolean,
290 NonNull,
291 <Self::Aggregate as AggregateKind>::Or<R::Aggregate>,
292 (
293 Arg<Self::Nullable, Self::Sources>,
294 Arg<R::Nullable, R::Sources>,
295 ),
296 >
297 where
298 R: Expr<'a, PostgresValue<'a>>,
299 Self::SQLType: ArrayOperand<R::SQLType>,
300 {
301 array_contained(self, other)
302 }
303
304 /// Tests whether `self` and `other` share at least one element (`&&`).
305 ///
306 /// See [`array_overlaps`].
307 #[allow(clippy::type_complexity)]
308 fn array_overlaps<R>(
309 self,
310 other: R,
311 ) -> SQLExpr<
312 'a,
313 PostgresValue<'a>,
314 Boolean,
315 NonNull,
316 <Self::Aggregate as AggregateKind>::Or<R::Aggregate>,
317 (
318 Arg<Self::Nullable, Self::Sources>,
319 Arg<R::Nullable, R::Sources>,
320 ),
321 >
322 where
323 R: Expr<'a, PostgresValue<'a>>,
324 Self::SQLType: ArrayOperand<R::SQLType>,
325 {
326 array_overlaps(self, other)
327 }
328}
329
330impl<'a, E: Expr<'a, PostgresValue<'a>>> ArrayExprExt<'a> for E {}