Skip to main content

drizzle_postgres/expr/
json_ops.rs

1//! `PostgreSQL` JSON/JSONB operators.
2//!
3//! Provides type-safe access to `PostgreSQL` JSON operators:
4//! - `->` (get JSON object field by key, returns JSON)
5//! - `->>` (get JSON object field by key, returns text)
6//! - `#>` (get JSON object at path, returns JSON)
7//! - `#>>` (get JSON object at path, returns text)
8//! - `@>` (JSON contains)
9//! - `?` (JSON key exists)
10
11#[cfg(not(feature = "std"))]
12use crate::prelude::*;
13use crate::values::PostgresValue;
14use drizzle_core::ToSQL;
15use drizzle_core::expr::{Expr, NonNull, Null, SQLExpr, Scalar};
16use drizzle_core::sql::{SQL, SQLChunk, Token};
17
18/// `CAST($n AS type)` around an operator argument.
19///
20/// PostgreSQL infers untyped parameters at prepare time and picks the `text`
21/// overload of `->` / `->>` / `#>`; without the cast the driver binds an
22/// integer or array where the server expects text and the query fails.
23/// `CAST(operand AS JSONB)` for containment operands.
24///
25/// A bound `serde_json::Value` is declared as `json` by the drivers and a text
26/// literal as `text`; neither resolves `jsonb @> ...` without the cast.
27fn jsonb_operand<'a>(operand: SQL<'a, PostgresValue<'a>>) -> SQL<'a, PostgresValue<'a>> {
28    SQL::func("CAST", operand.push(Token::AS).append(SQL::raw("JSONB")))
29}
30
31fn typed_param<'a>(
32    value: PostgresValue<'a>,
33    type_name: &'static str,
34) -> SQL<'a, PostgresValue<'a>> {
35    SQL::func(
36        "CAST",
37        SQL::param(value)
38            .push(Token::AS)
39            .append(SQL::raw(type_name)),
40    )
41}
42
43use drizzle_types::postgres::types::{Boolean, Json, Text};
44
45/// `PostgreSQL` `->` operator - get JSON object field by key, returns JSON.
46///
47/// # Example
48///
49/// ```
50/// # use drizzle_postgres::expr::json_get;
51/// # use drizzle_core::{SQL, ToSQL};
52/// # use drizzle_postgres::values::PostgresValue;
53/// let data = SQL::<PostgresValue>::raw("data");
54/// let field = json_get(data, "name");
55/// assert!(field.to_sql().sql().contains("->"));
56/// ```
57pub fn json_get<'a, E>(expr: E, key: &'a str) -> SQLExpr<'a, PostgresValue<'a>, Json, Null, Scalar>
58where
59    E: Expr<'a, PostgresValue<'a>>,
60{
61    SQLExpr::new(
62        expr.to_sql()
63            .push(SQLChunk::Raw("->".into()))
64            .append(typed_param(PostgresValue::Text(key.into()), "TEXT")),
65    )
66}
67
68/// `PostgreSQL` `->` operator with integer index - get JSON array element.
69///
70/// # Example
71///
72/// ```
73/// # use drizzle_postgres::expr::json_get_idx;
74/// # use drizzle_core::{SQL, ToSQL};
75/// # use drizzle_postgres::values::PostgresValue;
76/// let data = SQL::<PostgresValue>::raw("data");
77/// let elem = json_get_idx(data, 0);
78/// assert!(elem.to_sql().sql().contains("->"));
79/// ```
80pub fn json_get_idx<'a, E>(
81    expr: E,
82    index: i32,
83) -> SQLExpr<'a, PostgresValue<'a>, Json, Null, Scalar>
84where
85    E: Expr<'a, PostgresValue<'a>>,
86{
87    SQLExpr::new(
88        expr.to_sql()
89            .push(SQLChunk::Raw("->".into()))
90            .append(typed_param(PostgresValue::Integer(index), "INTEGER")),
91    )
92}
93
94/// `PostgreSQL` `->>` operator - get JSON object field as text.
95///
96/// # Example
97///
98/// ```
99/// # use drizzle_postgres::expr::json_get_text;
100/// # use drizzle_core::{SQL, ToSQL};
101/// # use drizzle_postgres::values::PostgresValue;
102/// let data = SQL::<PostgresValue>::raw("data");
103/// let name = json_get_text(data, "name");
104/// assert!(name.to_sql().sql().contains("->>"));
105/// ```
106pub fn json_get_text<'a, E>(
107    expr: E,
108    key: &'a str,
109) -> SQLExpr<'a, PostgresValue<'a>, Text, Null, Scalar>
110where
111    E: Expr<'a, PostgresValue<'a>>,
112{
113    SQLExpr::new(
114        expr.to_sql()
115            .push(SQLChunk::Raw("->>".into()))
116            .append(typed_param(PostgresValue::Text(key.into()), "TEXT")),
117    )
118}
119
120/// `PostgreSQL` `->>` operator with integer index - get JSON array element as text.
121///
122/// # Example
123///
124/// ```
125/// # use drizzle_postgres::expr::json_get_text_idx;
126/// # use drizzle_core::{SQL, ToSQL};
127/// # use drizzle_postgres::values::PostgresValue;
128/// let data = SQL::<PostgresValue>::raw("data");
129/// let elem = json_get_text_idx(data, 0);
130/// assert!(elem.to_sql().sql().contains("->>"));
131/// ```
132pub fn json_get_text_idx<'a, E>(
133    expr: E,
134    index: i32,
135) -> SQLExpr<'a, PostgresValue<'a>, Text, Null, Scalar>
136where
137    E: Expr<'a, PostgresValue<'a>>,
138{
139    SQLExpr::new(
140        expr.to_sql()
141            .push(SQLChunk::Raw("->>".into()))
142            .append(typed_param(PostgresValue::Integer(index), "INTEGER")),
143    )
144}
145
146/// `PostgreSQL` `#>` operator - get JSON object at specified path, returns JSON.
147///
148/// # Example
149///
150/// ```
151/// # use drizzle_postgres::expr::json_get_path;
152/// # use drizzle_core::{SQL, ToSQL};
153/// # use drizzle_postgres::values::PostgresValue;
154/// let data = SQL::<PostgresValue>::raw("data");
155/// let nested = json_get_path(data, "{a,b}");
156/// assert!(nested.to_sql().sql().contains("#>"));
157/// ```
158pub fn json_get_path<'a, E>(
159    expr: E,
160    path: &'a str,
161) -> SQLExpr<'a, PostgresValue<'a>, Json, Null, Scalar>
162where
163    E: Expr<'a, PostgresValue<'a>>,
164{
165    SQLExpr::new(
166        expr.to_sql()
167            .push(SQLChunk::Raw("#>".into()))
168            .append(typed_param(PostgresValue::Text(path.into()), "TEXT[]")),
169    )
170}
171
172/// `PostgreSQL` `#>>` operator - get JSON object at specified path as text.
173///
174/// # Example
175///
176/// ```
177/// # use drizzle_postgres::expr::json_get_path_text;
178/// # use drizzle_core::{SQL, ToSQL};
179/// # use drizzle_postgres::values::PostgresValue;
180/// let data = SQL::<PostgresValue>::raw("data");
181/// let nested = json_get_path_text(data, "{a,b}");
182/// assert!(nested.to_sql().sql().contains("#>>"));
183/// ```
184pub fn json_get_path_text<'a, E>(
185    expr: E,
186    path: &'a str,
187) -> SQLExpr<'a, PostgresValue<'a>, Text, Null, Scalar>
188where
189    E: Expr<'a, PostgresValue<'a>>,
190{
191    SQLExpr::new(
192        expr.to_sql()
193            .push(SQLChunk::Raw("#>>".into()))
194            .append(typed_param(PostgresValue::Text(path.into()), "TEXT[]")),
195    )
196}
197
198/// `PostgreSQL` `@>` operator for JSONB - left JSON contains right JSON.
199///
200/// # Example
201///
202/// ```
203/// # use drizzle_postgres::expr::jsonb_contains;
204/// # use drizzle_core::{SQL, ToSQL};
205/// # use drizzle_postgres::values::PostgresValue;
206/// let data = SQL::<PostgresValue>::raw("data");
207/// let cond = jsonb_contains(data, r#"{"key": "value"}"#);
208/// assert!(cond.to_sql().sql().contains("@>"));
209/// ```
210pub fn jsonb_contains<'a, L, R>(
211    left: L,
212    right: R,
213) -> SQLExpr<'a, PostgresValue<'a>, Boolean, NonNull, Scalar>
214where
215    L: Expr<'a, PostgresValue<'a>>,
216    R: ToSQL<'a, PostgresValue<'a>>,
217{
218    SQLExpr::new(
219        left.to_sql()
220            .push(SQLChunk::Raw("@>".into()))
221            .append(jsonb_operand(right.to_sql())),
222    )
223}
224
225/// `PostgreSQL` `<@` operator for JSONB - left JSON is contained by right JSON.
226///
227/// # Example
228///
229/// ```
230/// # use drizzle_postgres::expr::jsonb_contained;
231/// # use drizzle_core::{SQL, ToSQL};
232/// # use drizzle_postgres::values::PostgresValue;
233/// let data = SQL::<PostgresValue>::raw("data");
234/// let cond = jsonb_contained(data, r#"{"key": "value", "other": 1}"#);
235/// assert!(cond.to_sql().sql().contains("<@"));
236/// ```
237pub fn jsonb_contained<'a, L, R>(
238    left: L,
239    right: R,
240) -> SQLExpr<'a, PostgresValue<'a>, Boolean, NonNull, Scalar>
241where
242    L: Expr<'a, PostgresValue<'a>>,
243    R: ToSQL<'a, PostgresValue<'a>>,
244{
245    SQLExpr::new(
246        left.to_sql()
247            .push(SQLChunk::Raw("<@".into()))
248            .append(jsonb_operand(right.to_sql())),
249    )
250}
251
252/// `PostgreSQL` `?` operator for JSONB - does the key exist in the JSON object?
253///
254/// # Example
255///
256/// ```
257/// # use drizzle_postgres::expr::jsonb_exists_key;
258/// # use drizzle_core::{SQL, ToSQL};
259/// # use drizzle_postgres::values::PostgresValue;
260/// let data = SQL::<PostgresValue>::raw("data");
261/// let cond = jsonb_exists_key(data, "name");
262/// assert!(cond.to_sql().sql().contains("?"));
263/// ```
264pub fn jsonb_exists_key<'a, E>(
265    expr: E,
266    key: &'a str,
267) -> SQLExpr<'a, PostgresValue<'a>, Boolean, NonNull, Scalar>
268where
269    E: Expr<'a, PostgresValue<'a>>,
270{
271    SQLExpr::new(
272        expr.to_sql()
273            .push(SQLChunk::Raw("?".into()))
274            .append(typed_param(PostgresValue::Text(key.into()), "TEXT")),
275    )
276}
277
278/// `PostgreSQL` `?|` operator for JSONB - do any of the keys exist?
279///
280/// # Example
281///
282/// ```
283/// # use drizzle_postgres::expr::jsonb_exists_any;
284/// # use drizzle_core::{SQL, ToSQL};
285/// # use drizzle_postgres::values::PostgresValue;
286/// let data = SQL::<PostgresValue>::raw("data");
287/// let cond = jsonb_exists_any(data, &["name", "email"]);
288/// assert!(cond.to_sql().sql().contains("?|"));
289/// ```
290pub fn jsonb_exists_any<'a, E>(
291    expr: E,
292    keys: &[&'a str],
293) -> SQLExpr<'a, PostgresValue<'a>, Boolean, NonNull, Scalar>
294where
295    E: Expr<'a, PostgresValue<'a>>,
296{
297    let arr: Vec<PostgresValue<'a>> = keys
298        .iter()
299        .map(|k| PostgresValue::Text((*k).into()))
300        .collect();
301    SQLExpr::new(
302        expr.to_sql()
303            .push(SQLChunk::Raw("?|".into()))
304            .append(SQL::param(PostgresValue::Array(arr))),
305    )
306}
307
308/// `PostgreSQL` `?&` operator for JSONB - do all of the keys exist?
309///
310/// # Example
311///
312/// ```
313/// # use drizzle_postgres::expr::jsonb_exists_all;
314/// # use drizzle_core::{SQL, ToSQL};
315/// # use drizzle_postgres::values::PostgresValue;
316/// let data = SQL::<PostgresValue>::raw("data");
317/// let cond = jsonb_exists_all(data, &["name", "email"]);
318/// assert!(cond.to_sql().sql().contains("?&"));
319/// ```
320pub fn jsonb_exists_all<'a, E>(
321    expr: E,
322    keys: &[&'a str],
323) -> SQLExpr<'a, PostgresValue<'a>, Boolean, NonNull, Scalar>
324where
325    E: Expr<'a, PostgresValue<'a>>,
326{
327    let arr: Vec<PostgresValue<'a>> = keys
328        .iter()
329        .map(|k| PostgresValue::Text((*k).into()))
330        .collect();
331    SQLExpr::new(
332        expr.to_sql()
333            .push(SQLChunk::Raw("?&".into()))
334            .append(SQL::param(PostgresValue::Array(arr))),
335    )
336}
337
338/// Extension trait providing method-based JSON operators for `PostgreSQL` expressions.
339pub trait JsonExprExt<'a>: Expr<'a, PostgresValue<'a>> + Sized {
340    /// Get JSON object field by key (`->` operator), returns JSON.
341    fn json_get(self, key: &'a str) -> SQLExpr<'a, PostgresValue<'a>, Json, Null, Scalar> {
342        json_get(self, key)
343    }
344
345    /// Get JSON array element by index (`->` operator), returns JSON.
346    fn json_get_idx(self, index: i32) -> SQLExpr<'a, PostgresValue<'a>, Json, Null, Scalar> {
347        json_get_idx(self, index)
348    }
349
350    /// Get JSON object field as text (`->>` operator).
351    fn json_get_text(self, key: &'a str) -> SQLExpr<'a, PostgresValue<'a>, Text, Null, Scalar> {
352        json_get_text(self, key)
353    }
354
355    /// Get JSON array element as text (`->>` operator).
356    fn json_get_text_idx(self, index: i32) -> SQLExpr<'a, PostgresValue<'a>, Text, Null, Scalar> {
357        json_get_text_idx(self, index)
358    }
359
360    /// Get JSON object at path (`#>` operator), returns JSON.
361    fn json_get_path(self, path: &'a str) -> SQLExpr<'a, PostgresValue<'a>, Json, Null, Scalar> {
362        json_get_path(self, path)
363    }
364
365    /// Get JSON object at path as text (`#>>` operator).
366    fn json_get_path_text(
367        self,
368        path: &'a str,
369    ) -> SQLExpr<'a, PostgresValue<'a>, Text, Null, Scalar> {
370        json_get_path_text(self, path)
371    }
372
373    /// JSONB contains (`@>` operator).
374    fn jsonb_contains<R>(self, other: R) -> SQLExpr<'a, PostgresValue<'a>, Boolean, NonNull, Scalar>
375    where
376        R: ToSQL<'a, PostgresValue<'a>>,
377    {
378        jsonb_contains(self, other)
379    }
380
381    /// JSONB is contained by (`<@` operator).
382    fn jsonb_contained<R>(
383        self,
384        other: R,
385    ) -> SQLExpr<'a, PostgresValue<'a>, Boolean, NonNull, Scalar>
386    where
387        R: ToSQL<'a, PostgresValue<'a>>,
388    {
389        jsonb_contained(self, other)
390    }
391
392    /// JSONB key exists (`?` operator).
393    fn jsonb_exists_key(
394        self,
395        key: &'a str,
396    ) -> SQLExpr<'a, PostgresValue<'a>, Boolean, NonNull, Scalar> {
397        jsonb_exists_key(self, key)
398    }
399}
400
401/// Blanket implementation for all `PostgreSQL` `Expr` types.
402impl<'a, E: Expr<'a, PostgresValue<'a>>> JsonExprExt<'a> for E {}