Skip to main content

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 {}