Skip to main content

fraiseql_db/postgres/
where_generator.rs

1//! PostgreSQL WHERE clause SQL generation.
2//!
3//! `PostgresWhereGenerator` is a type alias for
4//! `GenericWhereGenerator<PostgresDialect>`.  All logic lives in
5//! [`crate::where_generator::GenericWhereGenerator`].
6
7use std::{
8    collections::{HashMap, HashSet},
9    sync::Arc,
10};
11
12use crate::{dialect::PostgresDialect, where_generator::GenericWhereGenerator};
13
14/// Cache of indexed columns for views.
15///
16/// This cache stores column names that follow the FraiseQL indexed column naming
17/// conventions:
18/// - Human-readable: `items__product__category__code` (double-underscore path)
19/// - Entity ID format: `f{entity_id}__{field_name}` (e.g., `f200100__code`)
20///
21/// When a WHERE clause references a nested path that has a corresponding indexed
22/// column, the generator uses the indexed column directly instead of JSONB
23/// extraction, enabling the database to use indexes for the query.
24///
25/// # Example
26///
27/// ```rust
28/// use fraiseql_db::postgres::IndexedColumnsCache;
29/// use std::collections::{HashMap, HashSet};
30///
31/// let mut cache = IndexedColumnsCache::new();
32///
33/// // Register indexed columns for a view
34/// let mut columns = HashSet::new();
35/// columns.insert("items__product__category__code".to_string());
36/// cache.insert("v_order_items".to_string(), columns);
37/// ```
38pub type IndexedColumnsCache = HashMap<String, HashSet<String>>;
39
40/// PostgreSQL WHERE clause generator.
41///
42/// Type alias for `GenericWhereGenerator<PostgresDialect>`.
43/// Refer to [`GenericWhereGenerator`] for full documentation.
44///
45/// # Example
46///
47/// ```rust
48/// use fraiseql_db::postgres::PostgresWhereGenerator;
49/// use fraiseql_db::{WhereClause, WhereOperator};
50/// use serde_json::json;
51///
52/// let generator = PostgresWhereGenerator::postgres_new();
53///
54/// let clause = WhereClause::Field {
55///     path: vec!["email".to_string()],
56///     operator: WhereOperator::Icontains,
57///     value: json!("example.com"),
58/// };
59///
60/// let (sql, params) = generator.generate(&clause).expect("Failed to generate SQL");
61/// // sql: "data->>'email' ILIKE '%' || $1 || '%'"
62/// ```
63pub type PostgresWhereGenerator = GenericWhereGenerator<PostgresDialect>;
64
65/// Constructor compatibility shim for `PostgresWhereGenerator`.
66///
67/// These `impl` blocks expose the same `new()` / `with_indexed_columns()`
68/// constructors that the old concrete struct had.
69impl PostgresWhereGenerator {
70    /// Create a new PostgreSQL WHERE generator.
71    #[must_use]
72    pub const fn postgres_new() -> Self {
73        Self::new(PostgresDialect)
74    }
75
76    /// Create a new PostgreSQL WHERE generator with indexed columns for a view.
77    ///
78    /// When indexed columns are provided, the generator uses them instead of
79    /// JSONB extraction for nested paths that have corresponding indexed columns.
80    ///
81    /// # Arguments
82    ///
83    /// * `indexed_columns` - Set of indexed column names for the current view
84    ///
85    /// # Example
86    ///
87    /// ```rust
88    /// use fraiseql_db::postgres::PostgresWhereGenerator;
89    /// use std::collections::HashSet;
90    /// use std::sync::Arc;
91    ///
92    /// let mut columns = HashSet::new();
93    /// columns.insert("items__product__category__code".to_string());
94    /// let generator = PostgresWhereGenerator::postgres_with_indexed_columns(Arc::new(columns));
95    /// ```
96    #[must_use]
97    pub fn postgres_with_indexed_columns(indexed_columns: Arc<HashSet<String>>) -> Self {
98        Self::new(PostgresDialect).with_indexed_columns(indexed_columns)
99    }
100}
101
102#[cfg(test)]
103mod tests;