postrust_core/plan/
read_plan.rs

1//! Read (SELECT) query planning.
2
3use super::types::*;
4use crate::api_request::{
5    ApiRequest, JoinType, QualifiedIdentifier, Range, SelectItem,
6};
7use crate::error::{Error, Result};
8use crate::schema_cache::{Relationship, SchemaCache, Table};
9use serde::{Deserialize, Serialize};
10
11/// A read plan for a single table/view.
12#[derive(Clone, Debug, Serialize, Deserialize)]
13pub struct ReadPlan {
14    /// Columns to select
15    pub select: Vec<CoercibleSelectField>,
16    /// Source table
17    pub from: QualifiedIdentifier,
18    /// Table alias
19    pub from_alias: Option<String>,
20    /// WHERE conditions
21    pub where_clauses: Vec<CoercibleLogicTree>,
22    /// ORDER BY terms
23    pub order: Vec<CoercibleOrderTerm>,
24    /// Pagination range
25    pub range: Range,
26    /// Relation name (for embedding)
27    pub rel_name: String,
28    /// Relationship to parent (if embedded)
29    pub rel_to_parent: Option<Relationship>,
30    /// Join conditions
31    pub rel_join_conds: Vec<JoinCondition>,
32    /// Join type
33    pub rel_join_type: Option<JoinType>,
34    /// Embedded relations to select
35    pub rel_select: Vec<RelSelectField>,
36    /// Nesting depth
37    pub depth: u32,
38}
39
40impl ReadPlan {
41    /// Create a read plan from an API request.
42    pub fn from_request(
43        request: &ApiRequest,
44        table: &Table,
45        schema_cache: &SchemaCache,
46    ) -> Result<Self> {
47        let qi = table.qualified_identifier();
48
49        // Build select fields
50        let select = build_select_fields(&request.query_params.select, table)?;
51
52        // Build where clauses from filters
53        let where_clauses = build_where_clauses(request, table)?;
54
55        // Build order terms
56        let order = build_order_terms(request, table)?;
57
58        // Build relation selects for embedding
59        let rel_select = build_relation_selects(&request.query_params.select, table, schema_cache)?;
60
61        Ok(Self {
62            select,
63            from: qi,
64            from_alias: None,
65            where_clauses,
66            order,
67            range: request.top_level_range.clone(),
68            rel_name: table.name.clone(),
69            rel_to_parent: None,
70            rel_join_conds: vec![],
71            rel_join_type: None,
72            rel_select,
73            depth: 0,
74        })
75    }
76
77    /// Create a read plan for returning mutation results.
78    pub fn for_mutation(
79        request: &ApiRequest,
80        table: &Table,
81        schema_cache: &SchemaCache,
82    ) -> Result<Self> {
83        let mut plan = Self::from_request(request, table, schema_cache)?;
84        // For mutations, we select from the CTE result
85        plan.from_alias = Some("pgrst_mutation_result".to_string());
86        Ok(plan)
87    }
88
89    /// Check if this plan has any where clauses.
90    pub fn has_where(&self) -> bool {
91        !self.where_clauses.is_empty()
92    }
93
94    /// Check if this plan has any order terms.
95    pub fn has_order(&self) -> bool {
96        !self.order.is_empty()
97    }
98
99    /// Check if this plan has pagination.
100    pub fn has_pagination(&self) -> bool {
101        self.range.limit.is_some() || self.range.offset > 0
102    }
103}
104
105/// Build select fields from select items.
106fn build_select_fields(
107    items: &[SelectItem],
108    table: &Table,
109) -> Result<Vec<CoercibleSelectField>> {
110    if items.is_empty() {
111        // Default: select all columns
112        return Ok(table
113            .columns
114            .iter()
115            .map(|(name, col)| CoercibleSelectField::simple(name, &col.data_type))
116            .collect());
117    }
118
119    let mut fields = Vec::new();
120
121    for item in items {
122        match item {
123            SelectItem::Field {
124                field,
125                aggregate,
126                aggregate_cast,
127                cast,
128                alias,
129            } => {
130                let column = table
131                    .get_column(&field.name)
132                    .ok_or_else(|| Error::ColumnNotFound(field.name.clone()))?;
133
134                fields.push(CoercibleSelectField {
135                    field: CoercibleField::from_field(field, &column.data_type),
136                    aggregate: aggregate.clone(),
137                    aggregate_cast: aggregate_cast.clone(),
138                    cast: cast.clone(),
139                    alias: alias.clone(),
140                });
141            }
142            // Relations are handled separately
143            SelectItem::Relation { .. } | SelectItem::SpreadRelation { .. } => {}
144        }
145    }
146
147    Ok(fields)
148}
149
150/// Build where clauses from request filters.
151fn build_where_clauses(
152    request: &ApiRequest,
153    table: &Table,
154) -> Result<Vec<CoercibleLogicTree>> {
155    let type_resolver = |name: &str| -> String {
156        table
157            .get_column(name)
158            .map(|c| c.data_type.clone())
159            .unwrap_or_else(|| "text".to_string())
160    };
161
162    let mut clauses = Vec::new();
163
164    // Add root filters
165    for filter in &request.query_params.filters_root {
166        let pg_type = type_resolver(&filter.field.name);
167        clauses.push(CoercibleLogicTree::Stmt(CoercibleFilter::from_filter(
168            filter, &pg_type,
169        )));
170    }
171
172    // Add logic trees
173    for (path, tree) in &request.query_params.logic {
174        if path.is_empty() {
175            clauses.push(CoercibleLogicTree::from_logic_tree(tree, type_resolver));
176        }
177    }
178
179    Ok(clauses)
180}
181
182/// Build order terms from request.
183fn build_order_terms(
184    request: &ApiRequest,
185    table: &Table,
186) -> Result<Vec<CoercibleOrderTerm>> {
187    let mut terms = Vec::new();
188
189    for (path, order_terms) in &request.query_params.order {
190        if path.is_empty() {
191            for term in order_terms {
192                let field_name = match term {
193                    crate::api_request::OrderTerm::Field { field, .. } => &field.name,
194                    crate::api_request::OrderTerm::Relation { field, .. } => &field.name,
195                };
196
197                let pg_type = table
198                    .get_column(field_name)
199                    .map(|c| c.data_type.as_str())
200                    .unwrap_or("text");
201
202                terms.push(CoercibleOrderTerm::from_order_term(term, pg_type));
203            }
204        }
205    }
206
207    Ok(terms)
208}
209
210/// Build relation select fields for embedding.
211fn build_relation_selects(
212    items: &[SelectItem],
213    table: &Table,
214    schema_cache: &SchemaCache,
215) -> Result<Vec<RelSelectField>> {
216    let mut rel_selects = Vec::new();
217
218    for item in items {
219        match item {
220            SelectItem::Relation {
221                relation,
222                alias,
223                hint: _,
224                join_type,
225            } => {
226                // Verify relationship exists
227                let _rel = schema_cache
228                    .find_relationship(&table.qualified_identifier(), relation, &table.schema)
229                    .ok_or_else(|| Error::RelationshipNotFound(relation.clone()))?;
230
231                rel_selects.push(RelSelectField {
232                    name: relation.clone(),
233                    agg_alias: alias.clone().unwrap_or_else(|| format!("pgrst_{}", relation)),
234                    join_type: join_type.clone().unwrap_or_default(),
235                    is_spread: false,
236                });
237            }
238            SelectItem::SpreadRelation {
239                relation,
240                hint: _,
241                join_type,
242            } => {
243                let _rel = schema_cache
244                    .find_relationship(&table.qualified_identifier(), relation, &table.schema)
245                    .ok_or_else(|| Error::RelationshipNotFound(relation.clone()))?;
246
247                rel_selects.push(RelSelectField {
248                    name: relation.clone(),
249                    agg_alias: format!("pgrst_spread_{}", relation),
250                    join_type: join_type.clone().unwrap_or_default(),
251                    is_spread: true,
252                });
253            }
254            _ => {}
255        }
256    }
257
258    Ok(rel_selects)
259}
260
261/// A tree of read plans (for nested embedding).
262#[derive(Clone, Debug)]
263pub struct ReadPlanTree {
264    /// Root plan
265    pub root: ReadPlan,
266    /// Child plans (embedded resources)
267    pub children: Vec<ReadPlanTree>,
268}
269
270impl ReadPlanTree {
271    /// Create an empty tree.
272    pub fn empty() -> Self {
273        Self {
274            root: ReadPlan {
275                select: vec![],
276                from: QualifiedIdentifier::unqualified(""),
277                from_alias: None,
278                where_clauses: vec![],
279                order: vec![],
280                range: Range::default(),
281                rel_name: String::new(),
282                rel_to_parent: None,
283                rel_join_conds: vec![],
284                rel_join_type: None,
285                rel_select: vec![],
286                depth: 0,
287            },
288            children: vec![],
289        }
290    }
291
292    /// Create a leaf tree (no children).
293    pub fn leaf(plan: ReadPlan) -> Self {
294        Self {
295            root: plan,
296            children: vec![],
297        }
298    }
299
300    /// Add a child tree.
301    pub fn add_child(&mut self, child: ReadPlanTree) {
302        self.children.push(child);
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn test_read_plan_tree_empty() {
312        let tree = ReadPlanTree::empty();
313        assert!(tree.root.select.is_empty());
314        assert!(tree.children.is_empty());
315    }
316}