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;