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: resolve_top_level_range(request),
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/// Resolve the effective top-level range.
146///
147/// The `Range` header supplies the base range; the `limit` and `offset` query
148/// parameters take precedence over it when present, matching PostgREST.
149fn resolve_top_level_range(request: &ApiRequest) -> Range {
150    let mut range = request.top_level_range.clone();
151
152    if let Some(from_params) = request.query_params.ranges.get("") {
153        if from_params.limit.is_some() {
154            range.limit = from_params.limit;
155        }
156        if from_params.offset != 0 {
157            range.offset = from_params.offset;
158        }
159    }
160
161    range
162}
163
164/// Build where clauses from request filters.
165fn build_where_clauses(request: &ApiRequest, table: &Table) -> Result<Vec<CoercibleLogicTree>> {
166    // `nominal_type` (the underlying `udt_name`) is used rather than `data_type`
167    // because it is always castable: `information_schema` reports arrays as
168    // `ARRAY` and enums as `USER-DEFINED`, neither valid in a `::type` cast.
169    let type_resolver = |name: &str| -> String {
170        table
171            .get_column(name)
172            .map(|c| c.nominal_type.clone())
173            .unwrap_or_else(|| "text".to_string())
174    };
175
176    let mut clauses = Vec::new();
177
178    // Add root filters
179    for filter in &request.query_params.filters_root {
180        let pg_type = type_resolver(&filter.field.name);
181        clauses.push(CoercibleLogicTree::Stmt(CoercibleFilter::from_filter(
182            filter, &pg_type,
183        )));
184    }
185
186    // Add logic trees
187    for (path, tree) in &request.query_params.logic {
188        if path.is_empty() {
189            clauses.push(CoercibleLogicTree::from_logic_tree(tree, type_resolver));
190        }
191    }
192
193    Ok(clauses)
194}
195
196/// Build order terms from request.
197fn build_order_terms(request: &ApiRequest, table: &Table) -> Result<Vec<CoercibleOrderTerm>> {
198    let mut terms = Vec::new();
199
200    for (path, order_terms) in &request.query_params.order {
201        if path.is_empty() {
202            for term in order_terms {
203                let field_name = match term {
204                    crate::api_request::OrderTerm::Field { field, .. } => &field.name,
205                    crate::api_request::OrderTerm::Relation { field, .. } => &field.name,
206                };
207
208                let pg_type = table
209                    .get_column(field_name)
210                    .map(|c| c.data_type.as_str())
211                    .unwrap_or("text");
212
213                terms.push(CoercibleOrderTerm::from_order_term(term, pg_type));
214            }
215        }
216    }
217
218    Ok(terms)
219}
220
221/// Build relation select fields for embedding.
222fn build_relation_selects(
223    items: &[SelectItem],
224    table: &Table,
225    schema_cache: &SchemaCache,
226) -> Result<Vec<RelSelectField>> {
227    let mut rel_selects = Vec::new();
228
229    for item in items {
230        match item {
231            SelectItem::Relation {
232                relation,
233                alias,
234                hint: _,
235                join_type,
236            } => {
237                // Verify relationship exists
238                let _rel = schema_cache
239                    .find_relationship(&table.qualified_identifier(), relation, &table.schema)
240                    .ok_or_else(|| Error::RelationshipNotFound(relation.clone()))?;
241
242                rel_selects.push(RelSelectField {
243                    name: relation.clone(),
244                    agg_alias: alias
245                        .clone()
246                        .unwrap_or_else(|| format!("pgrst_{}", relation)),
247                    join_type: join_type.clone().unwrap_or_default(),
248                    is_spread: false,
249                });
250            }
251            SelectItem::SpreadRelation {
252                relation,
253                hint: _,
254                join_type,
255            } => {
256                let _rel = schema_cache
257                    .find_relationship(&table.qualified_identifier(), relation, &table.schema)
258                    .ok_or_else(|| Error::RelationshipNotFound(relation.clone()))?;
259
260                rel_selects.push(RelSelectField {
261                    name: relation.clone(),
262                    agg_alias: format!("pgrst_spread_{}", relation),
263                    join_type: join_type.clone().unwrap_or_default(),
264                    is_spread: true,
265                });
266            }
267            _ => {}
268        }
269    }
270
271    Ok(rel_selects)
272}
273
274/// A tree of read plans (for nested embedding).
275#[derive(Clone, Debug)]
276pub struct ReadPlanTree {
277    /// Root plan
278    pub root: ReadPlan,
279    /// Child plans (embedded resources)
280    pub children: Vec<ReadPlanTree>,
281}
282
283impl ReadPlanTree {
284    /// Create an empty tree.
285    pub fn empty() -> Self {
286        Self {
287            root: ReadPlan {
288                select: vec![],
289                from: QualifiedIdentifier::unqualified(""),
290                from_alias: None,
291                where_clauses: vec![],
292                order: vec![],
293                range: Range::default(),
294                rel_name: String::new(),
295                rel_to_parent: None,
296                rel_join_conds: vec![],
297                rel_join_type: None,
298                rel_select: vec![],
299                depth: 0,
300            },
301            children: vec![],
302        }
303    }
304
305    /// Create a leaf tree (no children).
306    pub fn leaf(plan: ReadPlan) -> Self {
307        Self {
308            root: plan,
309            children: vec![],
310        }
311    }
312
313    /// Add a child tree.
314    pub fn add_child(&mut self, child: ReadPlanTree) {
315        self.children.push(child);
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322
323    #[test]
324    fn test_read_plan_tree_empty() {
325        let tree = ReadPlanTree::empty();
326        assert!(tree.root.select.is_empty());
327        assert!(tree.children.is_empty());
328    }
329}