zino_orm/
query.rs

1//! Query builder for SQL.
2//!
3//! # Design references
4//!
5//! The design of our ORM is inspired by [`Mongoose`], [`Prisma`], [`TypeORM`] and [`PostgREST`].
6//!
7//! ```rust,ignore
8//! use zino_core::{model::{Mutation, Query}, json, Map, Record};
9//! use zino_orm::{JoinOn, Schema};
10//!
11//! // Constructs a model `Query` with JSON expressions.
12//! let query = Query::new(json!({
13//!     "$or": [
14//!         {
15//!             "roles": "worker",
16//!             "visibility": "Public",
17//!         },
18//!         {
19//!             "roles": { "$in": ["admin", "auditor"] },
20//!             "visibility": { "$ne": "Public" },
21//!         },
22//!     ],
23//!     "status": { "$nin": ["Deleted", "Locked"] },
24//! }));
25//!
26//! // Constructs a model `Mutation` with JSON expressions.
27//! let mut mutation = Mutation::new(json!({
28//!     "status": "Active",
29//!     "refreshed_at": DateTime::now(),
30//!     "$inc": { "refresh_count": 1 },
31//! }));
32//!
33//! // Updates the models using `update_many` provided by the `Schema` trait.
34//! let ctx = User::update_many(&query, &mut mutation).await?;
35//! ctx.emit_metrics("user_refresh");
36//!
37//! // Constructs a model `Query` with projection fields.
38//! let mut query = Query::new(json!({
39//!     "project.start_date": { "$le": "2023-10-07" },
40//!     "project.end_date": { "$ge": "2023-10-01" },
41//!     "task.status": "Completed",
42//! }));
43//! query.allow_fields(&[
44//!     "task.id",
45//!     "task.name",
46//!     "task.status",
47//!     "task.project_id",
48//!     "project.start_date",
49//!     "project.end_date",
50//! ]);
51//! query.order_desc("task.updated_at");
52//!
53//! // Performs a LEFT OUTER JOIN using `lookup` provided by the `Schema` trait.
54//! let join_on = JoinOn::left_join().with("project_id", "id");
55//! let entries = Task::lookup::<Project, Map>(&query, &join_on).await?;
56//!
57//! // Executes the raw SQL with interpolations `${param}` and argument bindings `#{param}`.
58//! let sql =
59//!     "SELECT u.id, u.name, u.tags, t.id, t.name \
60//!         FROM ${user_table} u INNER JOIN ${tag_table} t \
61//!             ON t.id = ANY(u.tags) AND t.category = #{category};";
62//! let params = json!({
63//!     "user_table": User::table_name(),
64//!     "tag_table": Tag::table_name(),
65//!     "category": "Rustacean",
66//! });
67//! let records = User::query::<Record>(sql, params.as_object()).await?;
68//! ```
69//!
70//! # Query operators
71//!
72//! | Name       | MySQL               | PostgreSQL       | SQLite                |
73//! |------------|---------------------|------------------|-----------------------|
74//! | `$and`     | `AND`               | `AND`            | `AND`                 |
75//! | `$or`      | `OR`                | `OR`             | `OR`                  |
76//! | `$not`     | `NOT`               | `NOT`            | `NOT`                 |
77//! | `$rand`    | `rand()`            | `random()`       | `abs(random())`       |
78//! | `$text`    | `match() against()` | `to_tsvector()`  | `MATCH`               |
79//! | `$eq`      | `=`                 | `=`              | `=`                   |
80//! | `$ne`      | `<>`                | `<>`             | `<>`                  |
81//! | `$lt`      | `<`                 | `<`              | `<`                   |
82//! | `$le`      | `<=`                | `<=`             | `<=`                  |
83//! | `$gt`      | `>`                 | `>`              | `>`                   |
84//! | `$ge`      | `>=`                | `>=`             | `>=`                  |
85//! | `$in`      | `IN`                | `IN`             | `IN`                  |
86//! | `$nin`     | `NOT IN`            | `NOT IN`         | `NOT IN`              |
87//! | `$betw`    | `BETWEEN AND`       | `BETWEEN AND`    | `BETWEEN AND`         |
88//! | `$like`    | `LIKE`              | `LIKE`           | `LIKE`                |
89//! | `$ilike`   | `ILIKE`             | `ILIKE`          | `LOWER() LIKE`        |
90//! | `$rlike`   | `RLIKE`             | `~*`             | `REGEXP`              |
91//! | `$is`      | `IS`                | `IS`             | `IS`                  |
92//! | `$size`    | `json_length()`     | `array_length()` | `json_array_length()` |
93//!
94//! [`Mongoose`]: https://mongoosejs.com/
95//! [`Prisma`]: https://www.prisma.io/
96//! [`TypeORM`]: https://typeorm.io/
97//! [`PostgREST`]: https://postgrest.org/
98
99use super::{Aggregation, EncodeColumn, Entity, IntoSqlValue, ModelColumn, Schema, Window};
100use regex::{Captures, Regex};
101use std::{borrow::Cow, fmt::Display, marker::PhantomData};
102use zino_core::{
103    JsonValue, LazyLock, Map, SharedString,
104    extension::{JsonObjectExt, JsonValueExt},
105    model::{Query, QueryOrder},
106};
107
108/// A query builder for the model entity.
109///
110/// # Examples
111/// ```rust,ignore
112/// use crate::model::{User, UserColumn};
113/// use zino_core::orm::{QueryBuilder, Schema};
114///
115/// let query = QueryBuilder::new()
116///     .and_not_in(UserColumn::Status, ["Deleted", "Locked"])
117///     .or(QueryBuilder::new()
118///         .and_eq(UserColumn::Roles, "worker")
119///         .and_eq(UserColumn::Visibility, "Public"))
120///     .or(QueryBuilder::new()
121///         .and_in(UserColumn::Roles, ["admin", "auditor"])
122///         .and_ne(UserColumn::Visibility, "Public"))
123///     .order_desc(UserColumn::UpdatedAt)
124///     .limit(10)
125///     .build();
126/// let users: Vec<User> = User::find(&query).await?;
127/// ```
128#[derive(Debug, Clone)]
129pub struct QueryBuilder<E: Entity> {
130    /// The projection fields.
131    fields: Vec<String>,
132    /// The `GROUP BY` fields.
133    group_by_fields: Vec<String>,
134    /// The `HAVING` conditions.
135    having_conditions: Vec<Map>,
136    /// The filters.
137    filters: Map,
138    /// The logical `AND` conditions.
139    logical_and: Vec<Map>,
140    /// The logical `OR` conditions.
141    logical_or: Vec<Map>,
142    /// Sort order.
143    sort_order: Vec<QueryOrder>,
144    /// Offset.
145    offset: usize,
146    /// Limit.
147    limit: usize,
148    /// Extra flags.
149    extra: Map,
150    /// The phantom data.
151    phantom: PhantomData<E>,
152}
153
154impl<E: Entity> QueryBuilder<E> {
155    /// Creates a new instance.
156    #[inline]
157    pub fn new() -> Self {
158        Self {
159            fields: Vec::new(),
160            group_by_fields: Vec::new(),
161            having_conditions: Vec::new(),
162            filters: Map::new(),
163            logical_and: Vec::new(),
164            logical_or: Vec::new(),
165            sort_order: Vec::new(),
166            offset: 0,
167            limit: 0,
168            extra: Map::new(),
169            phantom: PhantomData,
170        }
171    }
172
173    /// Sets a table for the query, which should only be used to specify a dynamic table.
174    #[inline]
175    pub fn table_name(mut self, table_name: String) -> Self {
176        self.extra.upsert("table_name", table_name);
177        self
178    }
179
180    /// Adds a field corresponding to the column.
181    #[inline]
182    pub fn field<C: ModelColumn<E>>(mut self, col: C) -> Self {
183        self.fields.push(col.into_column_expr());
184        self
185    }
186
187    /// Adds the fields corresponding to the columns.
188    #[inline]
189    pub fn fields<C, V>(mut self, cols: V) -> Self
190    where
191        C: ModelColumn<E>,
192        V: Into<Vec<C>>,
193    {
194        let mut fields = cols
195            .into()
196            .into_iter()
197            .map(|col| col.into_column_expr())
198            .collect();
199        self.fields.append(&mut fields);
200        self
201    }
202
203    /// Adds a field with an alias for the column.
204    pub fn alias<C, F>(mut self, col: C, alias: F) -> Self
205    where
206        C: ModelColumn<E>,
207        F: AsRef<str>,
208    {
209        let col_name = col.into_column_expr();
210        let field = Query::format_field(&col_name);
211        let field_alias = [alias.as_ref(), ":", &field].concat();
212        self.fields.push(field_alias);
213        self
214    }
215
216    /// Adds a field with an optional alias for the aggregate expression.
217    pub fn aggregate<F: AsRef<str>>(
218        mut self,
219        aggregation: Aggregation<E>,
220        alias: Option<F>,
221    ) -> Self {
222        let expr = aggregation.expr();
223        let field_alias = if let Some(alias) = alias {
224            [alias.as_ref(), ":", &expr].concat()
225        } else {
226            let mut field_alias = aggregation.default_alias();
227            field_alias.push(':');
228            field_alias.push_str(&expr);
229            field_alias
230        };
231        self.fields.push(field_alias);
232        self
233    }
234
235    /// Adds a field with an optional alias for the window function.
236    pub fn window<F: AsRef<str>>(mut self, window: Window<E>, alias: Option<F>) -> Self {
237        let expr = window.expr();
238        let field_alias = if let Some(alias) = alias {
239            [alias.as_ref(), ":", &expr].concat()
240        } else {
241            let mut field_alias = window.default_alias();
242            field_alias.push(':');
243            field_alias.push_str(&expr);
244            field_alias
245        };
246        self.fields.push(field_alias);
247        self
248    }
249
250    /// Adds a `GROUP BY` column.
251    pub fn group_by<C, F>(mut self, col: C, alias: Option<F>) -> Self
252    where
253        C: ModelColumn<E>,
254        F: AsRef<str>,
255    {
256        let expr = col.into_column_expr();
257        let field = if let Some(alias) = alias {
258            [alias.as_ref(), ":", &expr].concat()
259        } else {
260            expr.clone()
261        };
262        if !self.fields.contains(&field) {
263            self.fields.push(field);
264        }
265        self.group_by_fields.push(expr);
266        self
267    }
268
269    /// Adds a `HAVING` condition using the value as a filter for the column.
270    #[inline]
271    pub fn having_filter<V: IntoSqlValue>(mut self, aggregation: Aggregation<E>, value: V) -> Self {
272        let condition = Map::from_entry(aggregation.expr(), value.into_sql_value());
273        self.having_conditions.push(condition);
274        self
275    }
276
277    /// Adds a `HAVING` condition for equal parts.
278    #[inline]
279    pub fn having_eq<V: IntoSqlValue>(self, aggregation: Aggregation<E>, value: V) -> Self {
280        self.push_having_condition(aggregation, "$eq", value.into_sql_value())
281    }
282
283    /// Adds a `HAVING` condition for non-equal parts.
284    #[inline]
285    pub fn having_ne<V: IntoSqlValue>(self, aggregation: Aggregation<E>, value: V) -> Self {
286        self.push_having_condition(aggregation, "$ne", value.into_sql_value())
287    }
288
289    /// Adds a `HAVING` condition for the column less than a value.
290    #[inline]
291    pub fn having_lt<V: IntoSqlValue>(self, aggregation: Aggregation<E>, value: V) -> Self {
292        self.push_having_condition(aggregation, "$lt", value.into_sql_value())
293    }
294
295    /// Adds a `HAVING` condition for the column not greater than a value.
296    #[inline]
297    pub fn having_le<V: IntoSqlValue>(self, aggregation: Aggregation<E>, value: V) -> Self {
298        self.push_having_condition(aggregation, "$le", value.into_sql_value())
299    }
300
301    /// Adds a `HAVING` condition for the column greater than a value.
302    #[inline]
303    pub fn having_gt<V: IntoSqlValue>(self, aggregation: Aggregation<E>, value: V) -> Self {
304        self.push_having_condition(aggregation, "$gt", value.into_sql_value())
305    }
306
307    /// Adds a `HAVING` condition for the column not less than a value.
308    #[inline]
309    pub fn having_ge<V: IntoSqlValue>(self, aggregation: Aggregation<E>, value: V) -> Self {
310        self.push_having_condition(aggregation, "$ge", value.into_sql_value())
311    }
312
313    /// Adds a logical `AND` condition for the primary key.
314    #[inline]
315    pub fn primary_key<V: IntoSqlValue>(mut self, value: V) -> Self {
316        let field = E::format_column(&E::PRIMARY_KEY);
317        self.filters.upsert(field, value.into_sql_value());
318        self
319    }
320
321    /// Adds a logical `AND` condition which selects random items by `rand() < value`.
322    #[inline]
323    pub fn rand<V: IntoSqlValue>(mut self, value: V) -> Self {
324        self.filters.upsert("$rand", value.into_sql_value());
325        self
326    }
327
328    /// Adds a logical `AND` condition by merging the other query builder.
329    pub fn and<M: Entity>(mut self, mut other: QueryBuilder<M>) -> Self {
330        let mut logical_and = other.logical_and;
331        let logical_or = other.logical_or;
332        if !logical_or.is_empty() {
333            logical_and.push(Map::from_entry("$or", logical_or));
334        }
335        if !logical_and.is_empty() {
336            self.logical_and.push(Map::from_entry("$and", logical_and));
337        }
338        self.fields.append(&mut other.fields);
339        self.group_by_fields.append(&mut other.group_by_fields);
340        self
341    }
342
343    /// Adds a logical `AND NOT` condition by merging the other query builder.
344    pub fn and_not<M: Entity>(mut self, mut other: QueryBuilder<M>) -> Self {
345        let mut logical_and = other.logical_and;
346        let logical_or = other.logical_or;
347        if !logical_or.is_empty() {
348            logical_and.push(Map::from_entry("$or", logical_or));
349        }
350        if !logical_and.is_empty() {
351            self.logical_and.push(Map::from_entry("$not", logical_and));
352        }
353        self.fields.append(&mut other.fields);
354        self.group_by_fields.append(&mut other.group_by_fields);
355        self
356    }
357
358    /// Adds a logical `AND` condition using the value as a filter for the column.
359    #[inline]
360    pub fn and_filter<C, V>(mut self, col: C, value: V) -> Self
361    where
362        C: ModelColumn<E>,
363        V: IntoSqlValue,
364    {
365        let condition = Map::from_entry(col.into_column_expr(), value.into_sql_value());
366        self.logical_and.push(condition);
367        self
368    }
369
370    /// Adds a logical `AND` condition for equal parts.
371    #[inline]
372    pub fn and_eq<C, V>(self, col: C, value: V) -> Self
373    where
374        C: ModelColumn<E>,
375        V: IntoSqlValue,
376    {
377        self.push_logical_and(col, "$eq", value.into_sql_value())
378    }
379
380    /// Adds a logical `AND` condition for equal parts if the value is not null.
381    #[inline]
382    pub fn and_eq_if_not_null<C, V>(self, col: C, value: V) -> Self
383    where
384        C: ModelColumn<E>,
385        V: IntoSqlValue,
386    {
387        let value = value.into_sql_value();
388        if !value.is_null() {
389            self.push_logical_and(col, "$eq", value)
390        } else {
391            self
392        }
393    }
394
395    /// Adds a logical `AND` condition for equal parts if the value is not none.
396    #[inline]
397    pub fn and_eq_if_some<C, V>(self, col: C, value: Option<V>) -> Self
398    where
399        C: ModelColumn<E>,
400        V: IntoSqlValue,
401    {
402        if let Some(value) = value {
403            self.push_logical_and(col, "$eq", value.into_sql_value())
404        } else {
405            self
406        }
407    }
408
409    /// Adds a logical `AND` condition for non-equal parts.
410    #[inline]
411    pub fn and_ne<C, V>(self, col: C, value: V) -> Self
412    where
413        C: ModelColumn<E>,
414        V: IntoSqlValue,
415    {
416        self.push_logical_and(col, "$ne", value.into_sql_value())
417    }
418
419    /// Adds a logical `AND` condition for non-equal parts if the value is not null.
420    #[inline]
421    pub fn and_ne_if_not_null<C, V>(self, col: C, value: V) -> Self
422    where
423        C: ModelColumn<E>,
424        V: IntoSqlValue,
425    {
426        let value = value.into_sql_value();
427        if !value.is_null() {
428            self.push_logical_and(col, "$ne", value)
429        } else {
430            self
431        }
432    }
433
434    /// Adds a logical `AND` condition for non-equal parts if the value is not none.
435    #[inline]
436    pub fn and_ne_if_some<C, V>(self, col: C, value: Option<V>) -> Self
437    where
438        C: ModelColumn<E>,
439        V: IntoSqlValue,
440    {
441        if let Some(value) = value {
442            self.push_logical_and(col, "$ne", value.into_sql_value())
443        } else {
444            self
445        }
446    }
447
448    /// Adds a logical `AND` condition for the column less than a value.
449    #[inline]
450    pub fn and_lt<C, V>(self, col: C, value: V) -> Self
451    where
452        C: ModelColumn<E>,
453        V: IntoSqlValue,
454    {
455        self.push_logical_and(col, "$lt", value.into_sql_value())
456    }
457
458    /// Adds a logical `AND` condition for the column not greater than a value.
459    #[inline]
460    pub fn and_le<C, V>(self, col: C, value: V) -> Self
461    where
462        C: ModelColumn<E>,
463        V: IntoSqlValue,
464    {
465        self.push_logical_and(col, "$le", value.into_sql_value())
466    }
467
468    /// Adds a logical `AND` condition for the column greater than a value.
469    #[inline]
470    pub fn and_gt<C, V>(self, col: C, value: V) -> Self
471    where
472        C: ModelColumn<E>,
473        V: IntoSqlValue,
474    {
475        self.push_logical_and(col, "$gt", value.into_sql_value())
476    }
477
478    /// Adds a logical `AND` condition for the column not less than a value.
479    #[inline]
480    pub fn and_ge<C, V>(self, col: C, value: V) -> Self
481    where
482        C: ModelColumn<E>,
483        V: IntoSqlValue,
484    {
485        self.push_logical_and(col, "$ge", value.into_sql_value())
486    }
487
488    /// Adds a logical `AND` condition for the column `IN` a list of values.
489    #[inline]
490    pub fn and_in<C, T, V>(self, col: C, values: V) -> Self
491    where
492        C: ModelColumn<E>,
493        T: IntoSqlValue,
494        V: Into<Vec<T>>,
495    {
496        self.push_logical_and(col, "$in", values.into().into_sql_value())
497    }
498
499    /// Adds a logical `AND` condition for the column `NOT IN` a list of values.
500    #[inline]
501    pub fn and_not_in<C, T, V>(self, col: C, values: V) -> Self
502    where
503        C: ModelColumn<E>,
504        T: IntoSqlValue,
505        V: Into<Vec<T>>,
506    {
507        self.push_logical_and(col, "$nin", values.into().into_sql_value())
508    }
509
510    /// Adds a logical `AND` condition for the column `IN` a list of values
511    /// if the list is nonempty.
512    #[inline]
513    pub fn and_in_if_nonempty<C, T, V>(self, col: C, values: V) -> Self
514    where
515        C: ModelColumn<E>,
516        T: IntoSqlValue,
517        V: Into<Vec<T>>,
518    {
519        let values = values.into();
520        if values.is_empty() {
521            self
522        } else {
523            self.push_logical_and(col, "$in", values.into_sql_value())
524        }
525    }
526
527    /// Adds a logical `AND` condition for the column `NOT IN` a list of values
528    /// if the list is nonempty.
529    #[inline]
530    pub fn and_not_in_if_nonempty<C, T, V>(self, col: C, values: V) -> Self
531    where
532        C: ModelColumn<E>,
533        T: IntoSqlValue,
534        V: Into<Vec<T>>,
535    {
536        let values = values.into();
537        if values.is_empty() {
538            self
539        } else {
540            self.push_logical_and(col, "$nin", values.into_sql_value())
541        }
542    }
543
544    /// Adds a logical `AND` condition for the columns `IN` a subquery.
545    pub fn and_in_subquery<C, V, M>(mut self, cols: V, subquery: QueryBuilder<M>) -> Self
546    where
547        C: ModelColumn<E>,
548        V: Into<Vec<C>>,
549        M: Entity + Schema,
550    {
551        let cols = cols
552            .into()
553            .into_iter()
554            .map(|col| {
555                let col_name = col.into_column_expr();
556                Query::format_field(&col_name).into_owned()
557            })
558            .collect::<Vec<_>>()
559            .join(", ");
560        let field = format!("({cols})");
561        let condition = Map::from_entry("$in", subquery.into_sql_value());
562        self.logical_and.push(Map::from_entry(field, condition));
563        self
564    }
565
566    /// Adds a logical `AND` condition for the columns `NOT IN` a subquery.
567    pub fn and_not_in_subquery<C, V, M>(mut self, cols: V, subquery: QueryBuilder<M>) -> Self
568    where
569        C: ModelColumn<E>,
570        V: Into<Vec<C>>,
571        M: Entity + Schema,
572    {
573        let cols = cols
574            .into()
575            .into_iter()
576            .map(|col| {
577                let col_name = col.into_column_expr();
578                Query::format_field(&col_name).into_owned()
579            })
580            .collect::<Vec<_>>()
581            .join(", ");
582        let field = format!("({cols})");
583        let condition = Map::from_entry("$nin", subquery.into_sql_value());
584        self.logical_and.push(Map::from_entry(field, condition));
585        self
586    }
587
588    /// Adds a logical `AND` condition for the column in a range `[min, max)`.
589    pub fn and_in_range<C, V>(mut self, col: C, min: V, max: V) -> Self
590    where
591        C: ModelColumn<E>,
592        V: IntoSqlValue,
593    {
594        let field = col.into_column_expr();
595        let mut condition = Map::new();
596        condition.upsert("$ge", min.into_sql_value());
597        condition.upsert("$lt", max.into_sql_value());
598        self.logical_and.push(Map::from_entry(field, condition));
599        self
600    }
601
602    /// Adds a logical `AND` condition for the column `BETWEEN` two values.
603    #[inline]
604    pub fn and_between<C, V>(self, col: C, min: V, max: V) -> Self
605    where
606        C: ModelColumn<E>,
607        V: IntoSqlValue,
608    {
609        self.push_logical_and(col, "$betw", [min, max].into_sql_value())
610    }
611
612    /// Adds a logical `AND` condition for the column `LIKE` a string value.
613    #[inline]
614    pub fn and_like<C: ModelColumn<E>>(self, col: C, value: String) -> Self {
615        self.push_logical_and(col, "$like", value.into_sql_value())
616    }
617
618    /// Adds a logical `AND` condition for the column `ILIKE` a string value.
619    #[inline]
620    pub fn and_ilike<C: ModelColumn<E>>(self, col: C, value: String) -> Self {
621        self.push_logical_and(col, "$ilike", value.into_sql_value())
622    }
623
624    /// Adds a logical `AND` condition for the column `RLIKE` a string value.
625    #[inline]
626    pub fn and_rlike<C: ModelColumn<E>>(self, col: C, value: String) -> Self {
627        self.push_logical_and(col, "$rlike", value.into_sql_value())
628    }
629
630    /// Adds a logical `AND` condition for the column which contains a string value.
631    #[inline]
632    pub fn and_contains<C: ModelColumn<E>>(self, col: C, value: &str) -> Self {
633        let value = ["%", value, "%"].concat();
634        self.push_logical_and(col, "$like", value.into_sql_value())
635    }
636
637    /// Adds a logical `AND` condition for the column which starts with a string value.
638    #[inline]
639    pub fn and_starts_with<C: ModelColumn<E>>(self, col: C, value: &str) -> Self {
640        let value = [value, "%"].concat();
641        self.push_logical_and(col, "$like", value.into_sql_value())
642    }
643
644    /// Adds a logical `AND` condition for the column which ends with a string value.
645    #[inline]
646    pub fn and_ends_with<C: ModelColumn<E>>(self, col: C, value: &str) -> Self {
647        let value = ["%", value].concat();
648        self.push_logical_and(col, "$like", value.into_sql_value())
649    }
650
651    /// Adds a logical `AND` condition for the column which is null.
652    #[inline]
653    pub fn and_null<C: ModelColumn<E>>(self, col: C) -> Self {
654        self.and_filter(col, JsonValue::Null)
655    }
656
657    /// Adds a logical `AND` condition for the column which is not null.
658    #[inline]
659    pub fn and_not_null<C: ModelColumn<E>>(self, col: C) -> Self {
660        self.and_filter(col, "not_null")
661    }
662
663    /// Adds a logical `AND` condition for the column which is an empty string or a null.
664    #[inline]
665    pub fn and_empty<C: ModelColumn<E>>(self, col: C) -> Self {
666        self.and_filter(col, "empty")
667    }
668
669    /// Adds a logical `AND` condition for the column which is not an empty string or a null.
670    #[inline]
671    pub fn and_nonempty<C: ModelColumn<E>>(self, col: C) -> Self {
672        self.and_filter(col, "nonempty")
673    }
674
675    /// Adds a logical `AND` condition for the two ranges which overlaps with each other.
676    pub fn and_overlaps<L, R, V>(mut self, cols: (L, R), values: (V, V)) -> Self
677    where
678        L: ModelColumn<E>,
679        R: ModelColumn<E>,
680        V: IntoSqlValue,
681    {
682        let mut condition = Map::new();
683        condition.upsert(
684            cols.0.into_column_expr(),
685            Map::from_entry("$le", values.1.into_sql_value()),
686        );
687        condition.upsert(
688            cols.1.into_column_expr(),
689            Map::from_entry("$ge", values.0.into_sql_value()),
690        );
691        self.logical_and.push(condition);
692        self
693    }
694
695    /// Adds a logical `OR` condition by merging the other query builder.
696    pub fn or<M: Entity>(mut self, mut other: QueryBuilder<M>) -> Self {
697        let mut logical_and = other.logical_and;
698        let logical_or = other.logical_or;
699        if !logical_or.is_empty() {
700            logical_and.push(Map::from_entry("$or", logical_or));
701        }
702        if !logical_and.is_empty() {
703            self.logical_or.push(Map::from_entry("$and", logical_and));
704        }
705        self.fields.append(&mut other.fields);
706        self.group_by_fields.append(&mut other.group_by_fields);
707        self
708    }
709
710    /// Adds a logical `OR NOT` condition by merging the other query builder.
711    pub fn or_not<M: Entity>(mut self, mut other: QueryBuilder<M>) -> Self {
712        let mut logical_and = other.logical_and;
713        let logical_or = other.logical_or;
714        if !logical_or.is_empty() {
715            logical_and.push(Map::from_entry("$or", logical_or));
716        }
717        if !logical_and.is_empty() {
718            self.logical_or.push(Map::from_entry("$not", logical_and));
719        }
720        self.fields.append(&mut other.fields);
721        self.group_by_fields.append(&mut other.group_by_fields);
722        self
723    }
724
725    /// Adds a logical `OR` condition using the value as a filter for the column.
726    #[inline]
727    pub fn or_filter<C, V>(mut self, col: C, value: V) -> Self
728    where
729        C: ModelColumn<E>,
730        V: IntoSqlValue,
731    {
732        let condition = Map::from_entry(col.into_column_expr(), value.into_sql_value());
733        self.logical_or.push(condition);
734        self
735    }
736
737    /// Adds a logical `OR` condition for equal parts.
738    #[inline]
739    pub fn or_eq<C, V>(self, col: C, value: V) -> Self
740    where
741        C: ModelColumn<E>,
742        V: IntoSqlValue,
743    {
744        self.push_logical_or(col, "$eq", value.into_sql_value())
745    }
746
747    /// Adds a logical `OR` condition for equal parts if the value is not null.
748    #[inline]
749    pub fn or_eq_if_not_null<C, V>(self, col: C, value: V) -> Self
750    where
751        C: ModelColumn<E>,
752        V: IntoSqlValue,
753    {
754        let value = value.into_sql_value();
755        if !value.is_null() {
756            self.push_logical_or(col, "$eq", value)
757        } else {
758            self
759        }
760    }
761
762    /// Adds a logical `OR` condition for equal parts if the value is not none.
763    #[inline]
764    pub fn or_eq_if_some<C, V>(self, col: C, value: Option<V>) -> Self
765    where
766        C: ModelColumn<E>,
767        V: IntoSqlValue,
768    {
769        if let Some(value) = value {
770            self.push_logical_or(col, "$eq", value.into_sql_value())
771        } else {
772            self
773        }
774    }
775
776    /// Adds a logical `OR` condition for non-equal parts.
777    #[inline]
778    pub fn or_ne<C, V>(self, col: C, value: V) -> Self
779    where
780        C: ModelColumn<E>,
781        V: IntoSqlValue,
782    {
783        self.push_logical_or(col, "$ne", value.into_sql_value())
784    }
785
786    /// Adds a logical `OR` condition for non-equal parts if the value is not none.
787    #[inline]
788    pub fn or_ne_if_not_null<C, V>(self, col: C, value: V) -> Self
789    where
790        C: ModelColumn<E>,
791        V: IntoSqlValue,
792    {
793        let value = value.into_sql_value();
794        if !value.is_null() {
795            self.push_logical_or(col, "$ne", value)
796        } else {
797            self
798        }
799    }
800
801    /// Adds a logical `OR` condition for non-equal parts if the value is not none.
802    #[inline]
803    pub fn or_ne_if_some<C, V>(self, col: C, value: Option<V>) -> Self
804    where
805        C: ModelColumn<E>,
806        V: IntoSqlValue,
807    {
808        if let Some(value) = value {
809            self.push_logical_or(col, "$ne", value.into_sql_value())
810        } else {
811            self
812        }
813    }
814
815    /// Adds a logical `OR` condition for the column less than a value.
816    #[inline]
817    pub fn or_lt<C, V>(self, col: C, value: V) -> Self
818    where
819        C: ModelColumn<E>,
820        V: IntoSqlValue,
821    {
822        self.push_logical_or(col, "$lt", value.into_sql_value())
823    }
824
825    /// Adds a logical `OR` condition for the column not greater than a value.
826    #[inline]
827    pub fn or_le<C, V>(self, col: C, value: V) -> Self
828    where
829        C: ModelColumn<E>,
830        V: IntoSqlValue,
831    {
832        self.push_logical_or(col, "$le", value.into_sql_value())
833    }
834
835    /// Adds a logical `OR` condition for the column greater than a value.
836    #[inline]
837    pub fn or_gt<C, V>(self, col: C, value: V) -> Self
838    where
839        C: ModelColumn<E>,
840        V: IntoSqlValue,
841    {
842        self.push_logical_or(col, "$gt", value.into_sql_value())
843    }
844
845    /// Adds a logical `OR` condition for the column not less than a value.
846    #[inline]
847    pub fn or_ge<C, V>(self, col: C, value: V) -> Self
848    where
849        C: ModelColumn<E>,
850        V: IntoSqlValue,
851    {
852        self.push_logical_or(col, "$ge", value.into_sql_value())
853    }
854
855    /// Adds a logical `OR` condition for the column `IN` a list of values.
856    #[inline]
857    pub fn or_in<C, T, V>(self, col: C, values: V) -> Self
858    where
859        C: ModelColumn<E>,
860        T: IntoSqlValue,
861        V: Into<Vec<T>>,
862    {
863        self.push_logical_or(col, "$in", values.into().into_sql_value())
864    }
865
866    /// Adds a logical `OR` condition for the column `NOT IN` a list of values.
867    #[inline]
868    pub fn or_not_in<C, T, V>(self, col: C, values: V) -> Self
869    where
870        C: ModelColumn<E>,
871        T: IntoSqlValue,
872        V: Into<Vec<T>>,
873    {
874        self.push_logical_or(col, "$nin", values.into().into_sql_value())
875    }
876
877    /// Adds a logical `OR` condition for the column `IN` a list of values
878    /// if the list is nonempty.
879    #[inline]
880    pub fn or_in_if_nonempty<C, T, V>(self, col: C, values: V) -> Self
881    where
882        C: ModelColumn<E>,
883        T: IntoSqlValue,
884        V: Into<Vec<T>>,
885    {
886        let values = values.into();
887        if values.is_empty() {
888            self
889        } else {
890            self.push_logical_or(col, "$in", values.into_sql_value())
891        }
892    }
893
894    /// Adds a logical `OR` condition for the column `NOT IN` a list of values
895    /// if the list is nonempty.
896    #[inline]
897    pub fn or_not_in_if_nonempty<C, T, V>(self, col: C, values: V) -> Self
898    where
899        C: ModelColumn<E>,
900        T: IntoSqlValue,
901        V: Into<Vec<T>>,
902    {
903        let values = values.into();
904        if values.is_empty() {
905            self
906        } else {
907            self.push_logical_or(col, "$nin", values.into_sql_value())
908        }
909    }
910
911    /// Adds a logical `OR` condition for the columns `IN` a subquery.
912    pub fn or_in_subquery<C, V, M>(mut self, cols: V, subquery: QueryBuilder<M>) -> Self
913    where
914        C: ModelColumn<E>,
915        V: Into<Vec<C>>,
916        M: Entity + Schema,
917    {
918        let cols = cols
919            .into()
920            .into_iter()
921            .map(|col| {
922                let col_name = col.into_column_expr();
923                Query::format_field(&col_name).into_owned()
924            })
925            .collect::<Vec<_>>()
926            .join(", ");
927        let field = format!("({cols})");
928        let condition = Map::from_entry("$in", subquery.into_sql_value());
929        self.logical_or.push(Map::from_entry(field, condition));
930        self
931    }
932
933    /// Adds a logical `OR` condition for the columns `NOT IN` a subquery.
934    pub fn or_not_in_subquery<C, V, M>(mut self, cols: V, subquery: QueryBuilder<M>) -> Self
935    where
936        C: ModelColumn<E>,
937        V: Into<Vec<C>>,
938        M: Entity + Schema,
939    {
940        let cols = cols
941            .into()
942            .into_iter()
943            .map(|col| {
944                let col_name = col.into_column_expr();
945                Query::format_field(&col_name).into_owned()
946            })
947            .collect::<Vec<_>>()
948            .join(", ");
949        let field = format!("({cols})");
950        let condition = Map::from_entry("$nin", subquery.into_sql_value());
951        self.logical_or.push(Map::from_entry(field, condition));
952        self
953    }
954
955    /// Adds a logical `OR` condition for the column is in a range `[min, max)`.
956    pub fn or_in_range<C, V>(mut self, col: C, min: V, max: V) -> Self
957    where
958        C: ModelColumn<E>,
959        V: IntoSqlValue,
960    {
961        let field = col.into_column_expr();
962        let mut condition = Map::new();
963        condition.upsert("$ge", min.into_sql_value());
964        condition.upsert("$lt", max.into_sql_value());
965        self.logical_or.push(Map::from_entry(field, condition));
966        self
967    }
968
969    /// Adds a logical `OR` condition for the column `BETWEEN` two values.
970    #[inline]
971    pub fn or_between<C, V>(self, col: C, min: V, max: V) -> Self
972    where
973        C: ModelColumn<E>,
974        V: IntoSqlValue,
975    {
976        self.push_logical_or(col, "$betw", [min, max].into_sql_value())
977    }
978
979    /// Adds a logical `OR` condition for the column `LIKE` a string value.
980    #[inline]
981    pub fn or_like<C: ModelColumn<E>>(self, col: C, value: String) -> Self {
982        self.push_logical_or(col, "$like", value.into_sql_value())
983    }
984
985    /// Adds a logical `OR` condition for the column `ILIKE` a string value.
986    #[inline]
987    pub fn or_ilike<C: ModelColumn<E>>(self, col: C, value: String) -> Self {
988        self.push_logical_or(col, "$ilike", value.into_sql_value())
989    }
990
991    /// Adds a logical `OR` condition for the column `RLIKE` a string value.
992    #[inline]
993    pub fn or_rlike<C: ModelColumn<E>>(self, col: C, value: String) -> Self {
994        self.push_logical_or(col, "$rlike", value.into_sql_value())
995    }
996
997    /// Adds a logical `OR` condition for the column which contains a string value.
998    #[inline]
999    pub fn or_contains<C: ModelColumn<E>>(self, col: C, value: &str) -> Self {
1000        let value = ["%", value, "%"].concat();
1001        self.push_logical_or(col, "$like", value.into_sql_value())
1002    }
1003
1004    /// Adds a logical `OR` condition for the column which starts with a string value.
1005    #[inline]
1006    pub fn or_starts_with<C: ModelColumn<E>>(self, col: C, value: &str) -> Self {
1007        let value = [value, "%"].concat();
1008        self.push_logical_or(col, "$like", value.into_sql_value())
1009    }
1010
1011    /// Adds a logical `OR` condition for the column which ends with a string value.
1012    #[inline]
1013    pub fn or_ends_with<C: ModelColumn<E>>(self, col: C, value: &str) -> Self {
1014        let value = ["%", value].concat();
1015        self.push_logical_or(col, "$like", value.into_sql_value())
1016    }
1017
1018    /// Adds a logical `OR` condition for the column which is null.
1019    #[inline]
1020    pub fn or_null<C: ModelColumn<E>>(self, col: C) -> Self {
1021        self.or_filter(col, JsonValue::Null)
1022    }
1023
1024    /// Adds a logical `OR` condition for the column which is not null.
1025    #[inline]
1026    pub fn or_not_null<C: ModelColumn<E>>(self, col: C) -> Self {
1027        self.or_filter(col, "not_null")
1028    }
1029
1030    /// Adds a logical `OR` condition for the column which is an empty string or a null.
1031    #[inline]
1032    pub fn or_empty<C: ModelColumn<E>>(self, col: C) -> Self {
1033        self.or_filter(col, "empty")
1034    }
1035
1036    /// Adds a logical `OR` condition for the column which is not an empty string or a null.
1037    #[inline]
1038    pub fn or_nonempty<C: ModelColumn<E>>(self, col: C) -> Self {
1039        self.or_filter(col, "nonempty")
1040    }
1041
1042    /// Adds a logical `OR` condition for the two ranges which overlaps with each other.
1043    pub fn or_overlaps<L, R, V>(mut self, cols: (L, R), values: (V, V)) -> Self
1044    where
1045        L: ModelColumn<E>,
1046        R: ModelColumn<E>,
1047        V: IntoSqlValue,
1048    {
1049        let mut condition = Map::new();
1050        condition.upsert(
1051            cols.0.into_column_expr(),
1052            Map::from_entry("$le", values.1.into_sql_value()),
1053        );
1054        condition.upsert(
1055            cols.1.into_column_expr(),
1056            Map::from_entry("$ge", values.0.into_sql_value()),
1057        );
1058        self.logical_or.push(condition);
1059        self
1060    }
1061
1062    /// Adds a query order.
1063    #[inline]
1064    pub fn order_by<C: ModelColumn<E>>(mut self, col: C, descending: bool) -> Self {
1065        self.sort_order
1066            .push(QueryOrder::new(col.into_column_expr(), descending));
1067        self
1068    }
1069
1070    /// Adds a query order with an extra flag to indicate whether the nulls appear first or last.
1071    #[inline]
1072    pub fn order_by_with_nulls<C: ModelColumn<E>>(
1073        mut self,
1074        col: C,
1075        descending: bool,
1076        nulls_first: bool,
1077    ) -> Self {
1078        let mut order = QueryOrder::new(col.into_column_expr(), descending);
1079        if nulls_first {
1080            order.set_nulls_first();
1081        } else {
1082            order.set_nulls_last();
1083        }
1084        self.sort_order.push(order);
1085        self
1086    }
1087
1088    /// Adds a query order with an ascending order.
1089    #[inline]
1090    pub fn order_asc<C: ModelColumn<E>>(mut self, col: C) -> Self {
1091        self.sort_order
1092            .push(QueryOrder::new(col.into_column_expr(), false));
1093        self
1094    }
1095
1096    /// Adds a query order with an descending order.
1097    #[inline]
1098    pub fn order_desc<C: ModelColumn<E>>(mut self, col: C) -> Self {
1099        self.sort_order
1100            .push(QueryOrder::new(col.into_column_expr(), true));
1101        self
1102    }
1103
1104    /// Sets the offset.
1105    #[inline]
1106    pub fn offset(mut self, offset: usize) -> Self {
1107        self.offset = offset;
1108        self
1109    }
1110
1111    /// Sets the limit.
1112    #[inline]
1113    pub fn limit(mut self, limit: usize) -> Self {
1114        self.limit = limit;
1115        self
1116    }
1117
1118    /// Builds the model query.
1119    pub fn build(mut self) -> Query {
1120        let mut filters = self.filters;
1121        let group_by_fields = self.group_by_fields;
1122        let having_conditions = self.having_conditions;
1123        let logical_and = self.logical_and;
1124        let logical_or = self.logical_or;
1125        if !group_by_fields.is_empty() {
1126            filters.upsert("$group", group_by_fields);
1127        }
1128        if !having_conditions.is_empty() {
1129            filters.upsert("$having", having_conditions);
1130        }
1131        if !logical_and.is_empty() {
1132            filters.upsert("$and", logical_and);
1133        }
1134        if !logical_or.is_empty() {
1135            filters.upsert("$or", logical_or);
1136        }
1137
1138        let mut query = Query::new(filters);
1139        query.set_fields(self.fields);
1140        query.set_order(self.sort_order);
1141        query.set_offset(self.offset);
1142        query.set_limit(self.limit);
1143        query.append_extra_flags(&mut self.extra);
1144        query
1145    }
1146
1147    /// Adds a `HAVING` condition for non-equal parts.
1148    fn push_having_condition(
1149        mut self,
1150        aggregation: Aggregation<E>,
1151        operator: &str,
1152        value: JsonValue,
1153    ) -> Self {
1154        let condition = Map::from_entry(operator, value);
1155        self.having_conditions
1156            .push(Map::from_entry(aggregation.expr(), condition));
1157        self
1158    }
1159
1160    /// Pushes a logical `AND` condition for the column and expressions.
1161    fn push_logical_and<C: ModelColumn<E>>(
1162        mut self,
1163        col: C,
1164        operator: &str,
1165        value: JsonValue,
1166    ) -> Self {
1167        let condition = Map::from_entry(operator, value);
1168        self.logical_and
1169            .push(Map::from_entry(col.into_column_expr(), condition));
1170        self
1171    }
1172
1173    /// Pushes a logical `OR` condition for the column and expressions.
1174    fn push_logical_or<C: ModelColumn<E>>(
1175        mut self,
1176        col: C,
1177        operator: &str,
1178        value: JsonValue,
1179    ) -> Self {
1180        let condition = Map::from_entry(operator, value);
1181        self.logical_or
1182            .push(Map::from_entry(col.into_column_expr(), condition));
1183        self
1184    }
1185}
1186
1187impl<E: Entity + Schema> QueryBuilder<E> {
1188    /// Builds a subquery SQL expression.
1189    #[inline]
1190    pub fn build_subquery(self) -> String {
1191        let query = self.build();
1192        let table_name = query.format_table_name::<E>();
1193        let projection = query.format_table_fields::<E>();
1194        let filters = query.format_filters::<E>();
1195        let sort = query.format_sort();
1196        let pagination = query.format_pagination();
1197        format!("(SELECT {projection} FROM {table_name} {filters} {sort} {pagination})")
1198    }
1199}
1200
1201impl<E: Entity> Default for QueryBuilder<E> {
1202    #[inline]
1203    fn default() -> Self {
1204        Self::new()
1205    }
1206}
1207
1208/// Extension trait for [`Query`](crate::model::Query).
1209pub(super) trait QueryExt<DB> {
1210    /// Query result type.
1211    type QueryResult;
1212
1213    /// Parses the query result to get `last_insert_id` and `rows_affected`.
1214    fn parse_query_result(query_result: Self::QueryResult) -> (Option<i64>, u64);
1215
1216    /// Returns a reference to the projection fields.
1217    fn query_fields(&self) -> &[String];
1218
1219    /// Returns a reference to the filters.
1220    fn query_filters(&self) -> &Map;
1221
1222    /// Returns the sort order.
1223    fn query_order(&self) -> &[QueryOrder];
1224
1225    /// Returns the query offset.
1226    fn query_offset(&self) -> usize;
1227
1228    /// Returns the query limit.
1229    fn query_limit(&self) -> usize;
1230
1231    /// Returns a placeholder for the n-th parameter.
1232    fn placeholder(n: usize) -> SharedString;
1233
1234    /// Prepares the SQL query for binding parameters.
1235    fn prepare_query<'a>(
1236        query: &'a str,
1237        params: Option<&'a Map>,
1238    ) -> (Cow<'a, str>, Vec<&'a JsonValue>);
1239
1240    /// Formats a field for the query.
1241    fn format_field(field: &str) -> Cow<'_, str>;
1242
1243    /// Formats table fields.
1244    fn format_table_fields<M: Schema>(&self) -> Cow<'_, str>;
1245
1246    /// Formats the table name.
1247    fn format_table_name<M: Schema>(&self) -> String;
1248
1249    /// Escapes the table name.
1250    fn escape_table_name(table_name: &str) -> String;
1251
1252    /// Parses text search filter.
1253    fn parse_text_search(filter: &Map) -> Option<String>;
1254
1255    /// Escapes a string.
1256    #[inline]
1257    fn escape_string(value: impl Display) -> String {
1258        format!("'{}'", value.to_string().replace('\'', "''"))
1259    }
1260
1261    /// Formats projection fields.
1262    fn format_projection(&self) -> Cow<'_, str> {
1263        let fields = self.query_fields();
1264        if fields.is_empty() {
1265            "*".into()
1266        } else {
1267            fields
1268                .iter()
1269                .map(|field| {
1270                    if let Some((alias, expr)) = field.split_once(':') {
1271                        let alias = Self::format_field(alias.trim());
1272                        format!(r#"{expr} AS {alias}"#).into()
1273                    } else {
1274                        Self::format_field(field)
1275                    }
1276                })
1277                .collect::<Vec<_>>()
1278                .join(", ")
1279                .into()
1280        }
1281    }
1282
1283    /// Formats the query filters to generate SQL `WHERE` expression.
1284    fn format_filters<M: Schema>(&self) -> String {
1285        let filters = self.query_filters();
1286        if filters.is_empty() {
1287            return String::new();
1288        }
1289
1290        let mut expression = String::new();
1291        let mut logical_and_conditions = Vec::with_capacity(filters.len());
1292        for (key, value) in filters {
1293            match key.as_str() {
1294                "$and" => {
1295                    if let Some(filters) = value.as_array() {
1296                        let condition = Self::format_logical_filters::<M>(filters, " AND ");
1297                        logical_and_conditions.push(condition);
1298                    }
1299                }
1300                "$not" => {
1301                    if let Some(filters) = value.as_array() {
1302                        let condition = Self::format_logical_filters::<M>(filters, " AND ");
1303                        logical_and_conditions.push(format!("(NOT {condition})"));
1304                    }
1305                }
1306                "$or" => {
1307                    if let Some(filters) = value.as_array() {
1308                        let condition = Self::format_logical_filters::<M>(filters, " OR ");
1309                        logical_and_conditions.push(condition);
1310                    }
1311                }
1312                "$rand" => {
1313                    if let Some(Ok(value)) = value.parse_f64() {
1314                        let condition = if cfg!(any(
1315                            feature = "orm-mariadb",
1316                            feature = "orm-mysql",
1317                            feature = "orm-tidb"
1318                        )) {
1319                            format!("rand() < {value}")
1320                        } else if cfg!(feature = "orm-postgres") {
1321                            format!("random() < {value}")
1322                        } else {
1323                            let value = (value * i64::MAX as f64) as i64;
1324                            format!("abs(random()) < {value}")
1325                        };
1326                        logical_and_conditions.push(condition);
1327                    }
1328                }
1329                "$text" => {
1330                    if let Some(condition) = value.as_object().and_then(Self::parse_text_search) {
1331                        logical_and_conditions.push(condition);
1332                    }
1333                }
1334                _ => {
1335                    if let Some(col) = M::get_column(key) {
1336                        let condition = if let Some(subquery) =
1337                            value.as_object().and_then(|m| m.get_str("$subquery"))
1338                        {
1339                            let key = Self::format_field(key);
1340                            format!(r#"{key} = {subquery}"#)
1341                        } else {
1342                            col.format_filter(key, value)
1343                        };
1344                        if !condition.is_empty() {
1345                            logical_and_conditions.push(condition);
1346                        }
1347                    } else if key.contains('.') {
1348                        let condition = Self::format_query_filter::<M>(key, value);
1349                        if !condition.is_empty() {
1350                            logical_and_conditions.push(condition);
1351                        }
1352                    }
1353                }
1354            }
1355        }
1356        if !logical_and_conditions.is_empty() {
1357            expression += &format!("WHERE {}", logical_and_conditions.join(" AND "));
1358        };
1359        if let Some(groups) = filters.parse_str_array("$group") {
1360            let groups = groups
1361                .into_iter()
1362                .map(Self::format_field)
1363                .collect::<Vec<_>>()
1364                .join(", ");
1365            expression += &format!(" GROUP BY {groups}");
1366            if let Some(filters) = filters.get_array("$having") {
1367                let condition = Self::format_logical_filters::<M>(filters, " AND ");
1368                expression += &format!(" HAVING {condition}");
1369            }
1370        }
1371        expression
1372    }
1373
1374    // Formats the filters with a logic operator.
1375    fn format_logical_filters<M: Schema>(filters: &[JsonValue], operator: &str) -> String {
1376        let mut conditions = Vec::with_capacity(filters.len());
1377        for filter in filters {
1378            if let JsonValue::Object(filter) = filter {
1379                let mut logical_and_conditions = Vec::with_capacity(filter.len());
1380                for (key, value) in filter {
1381                    match key.as_str() {
1382                        "$and" => {
1383                            if let Some(filters) = value.as_array() {
1384                                let condition = Self::format_logical_filters::<M>(filters, " AND ");
1385                                logical_and_conditions.push(condition);
1386                            }
1387                        }
1388                        "$not" => {
1389                            if let Some(filters) = value.as_array() {
1390                                let condition = Self::format_logical_filters::<M>(filters, " AND ");
1391                                logical_and_conditions.push(format!("(NOT {condition})"));
1392                            }
1393                        }
1394                        "$nor" => {
1395                            if let Some(filters) = value.as_array() {
1396                                let condition = Self::format_logical_filters::<M>(filters, " OR ");
1397                                logical_and_conditions.push(format!("(NOT {condition})"));
1398                            }
1399                        }
1400                        "$or" => {
1401                            if let Some(filters) = value.as_array() {
1402                                let condition = Self::format_logical_filters::<M>(filters, " OR ");
1403                                logical_and_conditions.push(condition);
1404                            }
1405                        }
1406                        _ => {
1407                            if let Some(col) = M::get_column(key) {
1408                                let condition = if let Some(subquery) =
1409                                    value.as_object().and_then(|m| m.get_str("$subquery"))
1410                                {
1411                                    let key = Self::format_field(key);
1412                                    format!(r#"{key} = {subquery}"#)
1413                                } else {
1414                                    col.format_filter(key, value)
1415                                };
1416                                if !condition.is_empty() {
1417                                    logical_and_conditions.push(condition);
1418                                }
1419                            } else if key.contains('.') {
1420                                let condition = Self::format_query_filter::<M>(key, value);
1421                                if !condition.is_empty() {
1422                                    logical_and_conditions.push(condition);
1423                                }
1424                            }
1425                        }
1426                    }
1427                }
1428                if !logical_and_conditions.is_empty() {
1429                    let condition = Self::join_conditions(logical_and_conditions, " AND ");
1430                    conditions.push(condition);
1431                }
1432            }
1433        }
1434        Self::join_conditions(conditions, operator)
1435    }
1436
1437    /// Formats a query filter.
1438    fn format_query_filter<M: Schema>(key: &str, value: &JsonValue) -> String {
1439        let json_field = key.split_once('.').and_then(|(key, path)| {
1440            M::get_column(key)
1441                .filter(|col| col.type_name() == "Map")
1442                .map(|col| {
1443                    let key = [M::model_name(), ".", col.name()].concat();
1444                    let field = Self::format_field(&key);
1445                    if cfg!(feature = "orm-postgres") {
1446                        let path = path.replace('.', ", ");
1447                        format!(r#"({field} #> '{{{path}}}')"#)
1448                    } else {
1449                        format!(r#"json_extract({field}, '$.{path}')"#)
1450                    }
1451                })
1452        });
1453        let requires_json_value = json_field.is_some();
1454        let field = json_field
1455            .map(|s| s.into())
1456            .unwrap_or_else(|| Self::format_field(key));
1457        if let Some(filter) = value.as_object() {
1458            let mut conditions = Vec::with_capacity(filter.len());
1459            for (name, value) in filter {
1460                let operator = match name.as_str() {
1461                    "$eq" => "=",
1462                    "$ne" => "<>",
1463                    "$lt" => "<",
1464                    "$le" => "<=",
1465                    "$gt" => ">",
1466                    "$ge" => ">=",
1467                    "$in" => "IN",
1468                    "$nin" => "NOT IN",
1469                    _ => "=",
1470                };
1471                let condition = if let Some(subquery) =
1472                    value.as_object().and_then(|m| m.get_str("$subquery"))
1473                {
1474                    format!(r#"{field} {operator} {subquery}"#)
1475                } else if requires_json_value {
1476                    Self::format_json_filter(&field, operator, value)
1477                } else if let Some(s) = value.as_str() {
1478                    if name == "$subquery" {
1479                        format!(r#"{field} {operator} {s}"#)
1480                    } else {
1481                        let value = Self::escape_string(s);
1482                        format!(r#"{field} {operator} {value}"#)
1483                    }
1484                } else {
1485                    format!(r#"{field} {operator} {value}"#)
1486                };
1487                conditions.push(condition);
1488            }
1489            Self::join_conditions(conditions, " AND ")
1490        } else if requires_json_value {
1491            Self::format_json_filter(&field, "=", value)
1492        } else if let Some(s) = value.as_str() {
1493            let value = Self::escape_string(s);
1494            format!(r#"{field} = {value}"#)
1495        } else {
1496            format!(r#"{field} = {value}"#)
1497        }
1498    }
1499
1500    /// Formats the filter for a JSON field.
1501    fn format_json_filter(field: &str, operator: &str, value: &JsonValue) -> String {
1502        match value {
1503            JsonValue::Null => format!(r#"{field} IS NULL"#),
1504            JsonValue::Bool(b) => {
1505                let value = if *b { "TRUE" } else { "FALSE" };
1506                if cfg!(feature = "orm-postgres") {
1507                    format!(r#"({field})::boolean IS {value}"#)
1508                } else {
1509                    format!(r#"{field} = {value}"#)
1510                }
1511            }
1512            JsonValue::Number(n) => {
1513                if cfg!(feature = "orm-postgres") {
1514                    format!(r#"{field} {operator} '{n}'"#)
1515                } else {
1516                    format!(r#"{field} {operator} {n}"#)
1517                }
1518            }
1519            JsonValue::String(s) => {
1520                if s == "null" {
1521                    format!(r#"{field} IS NULL"#)
1522                } else if s == "not_null" {
1523                    format!(r#"{field} IS NOT NULL"#)
1524                } else if s == "true" || s == "false" {
1525                    if cfg!(feature = "orm-postgres") {
1526                        format!(r#"({field})::boolean IS {s}"#)
1527                    } else {
1528                        format!(r#"{field} = {s}"#)
1529                    }
1530                } else if let Ok(n) = s.parse::<serde_json::Number>() {
1531                    if cfg!(feature = "orm-postgres") {
1532                        format!(r#"{field} {operator} '{n}'"#)
1533                    } else {
1534                        format!(r#"{field} {operator} {n}"#)
1535                    }
1536                } else {
1537                    let value = if cfg!(feature = "orm-postgres") {
1538                        Self::escape_string(value)
1539                    } else {
1540                        Self::escape_string(s)
1541                    };
1542                    format!(r#"{field} {operator} {value}"#)
1543                }
1544            }
1545            _ => {
1546                let value = Self::escape_string(value);
1547                format!(r#"{field} {operator} {value}"#)
1548            }
1549        }
1550    }
1551
1552    /// Formats the query sort to generate SQL `ORDER BY` expression.
1553    fn format_sort(&self) -> String {
1554        let sort_order = self.query_order();
1555        if sort_order.is_empty() {
1556            String::new()
1557        } else {
1558            let sort_order = sort_order
1559                .iter()
1560                .map(|order| {
1561                    let sort_field = Self::format_field(order.field());
1562                    let mut expr = if order.is_descending() {
1563                        format!("{sort_field} DESC")
1564                    } else {
1565                        format!("{sort_field} ASC")
1566                    };
1567                    if order.nulls_first() {
1568                        expr.push_str(" NULLS FIRST");
1569                    } else if order.nulls_last() {
1570                        expr.push_str(" NULLS LAST");
1571                    }
1572                    expr
1573                })
1574                .collect::<Vec<_>>();
1575            format!("ORDER BY {}", sort_order.join(", "))
1576        }
1577    }
1578
1579    /// Formats the query pagination to generate SQL `LIMIT` expression.
1580    fn format_pagination(&self) -> String {
1581        let limit = self.query_limit();
1582        if limit == 0 || limit == usize::MAX {
1583            return String::new();
1584        }
1585
1586        let offset = self.query_offset();
1587        format!("LIMIT {limit} OFFSET {offset}")
1588    }
1589
1590    /// Joins the conditions.
1591    fn join_conditions(mut conditions: Vec<String>, operator: &str) -> String {
1592        match conditions.len() {
1593            0 => String::new(),
1594            1 => conditions.remove(0),
1595            _ => format!("({})", conditions.join(operator)),
1596        }
1597    }
1598}
1599
1600/// Formats the query using interpolation of the parameters.
1601///
1602/// The interpolation parameter is represented as `${param}`,
1603/// in which `param` can only contain restricted chracters `[a-zA-Z]+[\w\.]*`.
1604pub(crate) fn format_query<'a>(query: &'a str, params: Option<&'a Map>) -> Cow<'a, str> {
1605    if let Some(params) = params.filter(|_| query.contains('$')) {
1606        INTERPOLATION_PATTERN.replace_all(query, |captures: &Captures| {
1607            let key = &captures[1];
1608            params
1609                .get(key)
1610                .map(|value| match value {
1611                    JsonValue::String(s) => s.to_owned(),
1612                    _ => value.to_string(),
1613                })
1614                .unwrap_or_else(|| ["${", key, "}"].concat())
1615        })
1616    } else {
1617        Cow::Borrowed(query)
1618    }
1619}
1620
1621/// Prepares the SQL query for binding parameters
1622/// (`?` for most SQL flavors and `$N` for PostgreSQL).
1623///
1624/// The parameter is represented as `${param}` or `#{param}`,
1625/// in which `param` can only contain restricted chracters `[a-zA-Z]+[\w\.]*`.
1626pub(crate) fn prepare_sql_query<'a>(
1627    query: &'a str,
1628    params: Option<&'a Map>,
1629    placeholder: char,
1630) -> (Cow<'a, str>, Vec<&'a JsonValue>) {
1631    let sql = format_query(query, params);
1632    if let Some(params) = params.filter(|_| sql.contains('#')) {
1633        let mut values = Vec::new();
1634        let sql = STATEMENT_PATTERN.replace_all(&sql, |captures: &Captures| {
1635            let key = &captures[1];
1636            let value = params.get(key).unwrap_or(&JsonValue::Null);
1637            values.push(value);
1638            if placeholder == '$' {
1639                Cow::Owned(format!("${}", values.len()))
1640            } else {
1641                Cow::Borrowed("?")
1642            }
1643        });
1644        (sql.into_owned().into(), values)
1645    } else {
1646        (sql, Vec::new())
1647    }
1648}
1649
1650/// Regex for the interpolation parameter.
1651static INTERPOLATION_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
1652    Regex::new(r"\$\{\s*([a-zA-Z]+[\w\.]*)\s*\}")
1653        .expect("fail to create a regex for the interpolation parameter")
1654});
1655
1656/// Regex for the prepared statement.
1657static STATEMENT_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
1658    Regex::new(r"\#\{\s*([a-zA-Z]+[\w\.]*)\s*\}")
1659        .expect("fail to create a regex for the prepared statement")
1660});
1661
1662#[cfg(test)]
1663mod tests {
1664    use zino_core::{Map, extension::JsonObjectExt};
1665
1666    #[test]
1667    fn it_formats_query_params() {
1668        let query = "SELECT ${fields} FROM users WHERE name = 'alice' AND age >= #{age};";
1669        let mut params = Map::new();
1670        params.upsert("fields", "id, name, age");
1671        params.upsert("age", 18);
1672
1673        let sql = super::format_query(query, Some(&params));
1674        assert_eq!(
1675            sql,
1676            "SELECT id, name, age FROM users WHERE name = 'alice' AND age >= #{age};"
1677        );
1678    }
1679
1680    #[test]
1681    fn it_formats_sql_query_params() {
1682        let query = "SELECT ${fields} FROM users WHERE name = 'alice' AND age >= #{age};";
1683        let mut params = Map::new();
1684        params.upsert("fields", "id, name, age");
1685        params.upsert("age", 18);
1686
1687        let (sql, values) = super::prepare_sql_query(query, Some(&params), '?');
1688        assert_eq!(
1689            sql,
1690            "SELECT id, name, age FROM users WHERE name = 'alice' AND age >= ?;"
1691        );
1692        assert_eq!(values[0], 18);
1693
1694        let (sql, values) = super::prepare_sql_query(query, Some(&params), '$');
1695        assert_eq!(
1696            sql,
1697            "SELECT id, name, age FROM users WHERE name = 'alice' AND age >= $1;"
1698        );
1699        assert_eq!(values[0], 18);
1700    }
1701}