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