Skip to main content

drizzle_postgres/expr/
array_ops.rs

1//! PostgreSQL array operators.
2//!
3//! This module provides PostgreSQL-specific array operators:
4//! - `@>` (contains)
5//! - `<@` (contained by)
6//! - `&&` (overlaps)
7//!
8//! # Example
9//!
10//! ```
11//! # use drizzle_postgres::expr::array_contains;
12//! # use drizzle_core::{SQL, ToSQL};
13//! # use drizzle_postgres::values::PostgresValue;
14//! let tags = SQL::<PostgresValue>::raw("tags");
15//! let condition = array_contains(tags, "test");
16//! assert!(condition.to_sql().sql().contains("@>"));
17//! ```
18
19use crate::values::PostgresValue;
20use drizzle_core::ToSQL;
21use drizzle_core::expr::{Expr, NonNull, SQLExpr, Scalar};
22use drizzle_core::sql::{SQL, SQLChunk};
23use drizzle_core::types::Bool;
24
25/// Wrapper for passing a `Vec<T>` as a single PostgreSQL array parameter.
26///
27/// Without this wrapper, `Vec<T>` implements `ToSQL` by joining elements
28/// with commas (`$1, $2, $3`), which is correct for `IN (...)` clauses
29/// but wrong for array operators like `@>`, `<@`, and `&&` which expect
30/// a single array parameter.
31///
32/// # Example
33///
34/// ```
35/// # use drizzle_postgres::expr::{array_contains, PgArray};
36/// # use drizzle_core::{SQL, ToSQL};
37/// # use drizzle_postgres::values::PostgresValue;
38/// let tags = SQL::<PostgresValue>::raw("tags");
39/// // Correct: passes as a single array parameter
40/// let condition = array_contains(tags, PgArray(vec!["rust", "python"]));
41/// ```
42pub struct PgArray<T>(pub Vec<T>);
43
44impl<'a, T> ToSQL<'a, PostgresValue<'a>> for PgArray<T>
45where
46    T: Into<PostgresValue<'a>> + Clone,
47{
48    fn to_sql(&self) -> SQL<'a, PostgresValue<'a>> {
49        let array: Vec<PostgresValue<'a>> = self.0.iter().map(|v| v.clone().into()).collect();
50        SQL::param(PostgresValue::Array(array))
51    }
52}
53
54/// PostgreSQL `@>` operator - array contains.
55///
56/// Returns true if the left array contains all elements of the right array.
57///
58/// # Example
59///
60/// ```
61/// # use drizzle_postgres::expr::array_contains;
62/// # use drizzle_core::{SQL, ToSQL};
63/// # use drizzle_postgres::values::PostgresValue;
64/// let tags = SQL::<PostgresValue>::raw("tags");
65/// let condition = array_contains(tags, "rust");
66/// assert!(condition.to_sql().sql().contains("@>"));
67/// // Generates: tags @> $1
68/// ```
69pub fn array_contains<'a, L, R>(
70    left: L,
71    right: R,
72) -> SQLExpr<'a, PostgresValue<'a>, Bool, NonNull, Scalar>
73where
74    L: Expr<'a, PostgresValue<'a>>,
75    R: ToSQL<'a, PostgresValue<'a>>,
76{
77    SQLExpr::new(
78        left.to_sql()
79            .push(SQLChunk::Raw("@>".into()))
80            .append(right.to_sql()),
81    )
82}
83
84/// PostgreSQL `<@` operator - array is contained by.
85///
86/// Returns true if the left array is contained by the right array
87/// (i.e., all elements of left are in right).
88///
89/// # Example
90///
91/// ```
92/// # use drizzle_postgres::expr::array_contained;
93/// # use drizzle_core::{SQL, ToSQL};
94/// # use drizzle_postgres::values::PostgresValue;
95/// let tags = SQL::<PostgresValue>::raw("tags");
96/// let condition = array_contained(tags, "rust");
97/// assert!(condition.to_sql().sql().contains("<@"));
98/// // Generates: tags <@ $1
99/// ```
100pub fn array_contained<'a, L, R>(
101    left: L,
102    right: R,
103) -> SQLExpr<'a, PostgresValue<'a>, Bool, NonNull, Scalar>
104where
105    L: Expr<'a, PostgresValue<'a>>,
106    R: ToSQL<'a, PostgresValue<'a>>,
107{
108    SQLExpr::new(
109        left.to_sql()
110            .push(SQLChunk::Raw("<@".into()))
111            .append(right.to_sql()),
112    )
113}
114
115/// PostgreSQL `&&` operator - arrays overlap.
116///
117/// Returns true if the arrays have any elements in common.
118///
119/// # Example
120///
121/// ```
122/// # use drizzle_postgres::expr::array_overlaps;
123/// # use drizzle_core::{SQL, ToSQL};
124/// # use drizzle_postgres::values::PostgresValue;
125/// let tags = SQL::<PostgresValue>::raw("tags");
126/// let condition = array_overlaps(tags, "rust");
127/// assert!(condition.to_sql().sql().contains("&&"));
128/// // Generates: tags && $1
129/// ```
130pub fn array_overlaps<'a, L, R>(
131    left: L,
132    right: R,
133) -> SQLExpr<'a, PostgresValue<'a>, Bool, NonNull, Scalar>
134where
135    L: Expr<'a, PostgresValue<'a>>,
136    R: ToSQL<'a, PostgresValue<'a>>,
137{
138    SQLExpr::new(
139        left.to_sql()
140            .push(SQLChunk::Raw("&&".into()))
141            .append(right.to_sql()),
142    )
143}
144
145/// Extension trait providing method-based array operators for PostgreSQL expressions.
146///
147/// This trait provides `.array_contains()`, `.array_contained()`, and `.array_overlaps()`
148/// methods on any expression type.
149///
150/// # Example
151///
152/// ```
153/// # use drizzle_postgres::expr::ArrayExprExt;
154/// # use drizzle_core::{SQL, ToSQL};
155/// # use drizzle_postgres::values::PostgresValue;
156/// let tags = SQL::<PostgresValue>::raw("tags");
157/// let condition = tags.array_contains("rust");
158/// assert!(condition.to_sql().sql().contains("@>"));
159/// ```
160pub trait ArrayExprExt<'a>: Expr<'a, PostgresValue<'a>> + Sized {
161    /// PostgreSQL `@>` operator - array contains.
162    ///
163    /// Returns true if self contains all elements of the other array.
164    fn array_contains<R>(self, other: R) -> SQLExpr<'a, PostgresValue<'a>, Bool, NonNull, Scalar>
165    where
166        R: ToSQL<'a, PostgresValue<'a>>,
167    {
168        array_contains(self, other)
169    }
170
171    /// PostgreSQL `<@` operator - array is contained by.
172    ///
173    /// Returns true if self is contained by the other array.
174    fn array_contained<R>(self, other: R) -> SQLExpr<'a, PostgresValue<'a>, Bool, NonNull, Scalar>
175    where
176        R: ToSQL<'a, PostgresValue<'a>>,
177    {
178        array_contained(self, other)
179    }
180
181    /// PostgreSQL `&&` operator - arrays overlap.
182    ///
183    /// Returns true if self and the other array have any elements in common.
184    fn array_overlaps<R>(self, other: R) -> SQLExpr<'a, PostgresValue<'a>, Bool, NonNull, Scalar>
185    where
186        R: ToSQL<'a, PostgresValue<'a>>,
187    {
188        array_overlaps(self, other)
189    }
190}
191
192/// Blanket implementation for all PostgreSQL `Expr` types.
193impl<'a, E: Expr<'a, PostgresValue<'a>>> ArrayExprExt<'a> for E {}