Skip to main content

postrust_core/plan/
read_plan.rs

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