1use 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#[derive(Clone, Debug, Serialize, Deserialize)]
11pub struct ReadPlan {
12 pub select: Vec<CoercibleSelectField>,
14 pub from: QualifiedIdentifier,
16 pub from_alias: Option<String>,
18 pub where_clauses: Vec<CoercibleLogicTree>,
20 pub order: Vec<CoercibleOrderTerm>,
22 pub range: Range,
24 pub rel_name: String,
26 pub rel_to_parent: Option<Relationship>,
28 pub rel_join_conds: Vec<JoinCondition>,
30 pub rel_join_type: Option<JoinType>,
32 pub rel_select: Vec<RelSelectField>,
34 pub depth: u32,
36}
37
38impl ReadPlan {
39 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 let mut select = build_select_fields(&request.query_params.select, table)?;
49
50 for field in select.iter_mut() {
55 if field.field.transform.is_some() || field.aggregate.is_some() {
56 continue;
57 }
58 let Some(column) = table.get_column(&field.field.name) else {
59 continue;
60 };
61 if let Some(function) =
62 schema_cache.representation(column.representation_type(), "json")
63 {
64 field.field.transform = Some(function.to_string());
65 }
66 }
67
68 if request.preferences.timezone.is_some() {
74 for field in select.iter_mut() {
75 if field.cast.is_none()
76 && field.aggregate.is_none()
77 && field.field.json_path.is_empty()
78 && matches!(
79 field.field.ir_type.as_str(),
80 "timestamptz"
81 | "timetz"
82 | "timestamp with time zone"
83 | "time with time zone"
84 )
85 {
86 field.field.transform = Some("to_jsonb".to_string());
87 }
88 }
89 }
90
91 let mut where_clauses = build_where_clauses(request, table)?;
93 for clause in where_clauses.iter_mut() {
94 attach_representation_tree(clause, table, schema_cache);
95 }
96
97 let order = build_order_terms(request, table)?;
99
100 let rel_select = build_relation_selects(&request.query_params.select, table, schema_cache)?;
102
103 Ok(Self {
104 select,
105 from: qi,
106 from_alias: None,
107 where_clauses,
108 order,
109 range: resolve_top_level_range(request),
110 rel_name: table.name.clone(),
111 rel_to_parent: None,
112 rel_join_conds: vec![],
113 rel_join_type: None,
114 rel_select,
115 depth: 0,
116 })
117 }
118
119 pub fn for_mutation(
121 request: &ApiRequest,
122 table: &Table,
123 schema_cache: &SchemaCache,
124 ) -> Result<Self> {
125 let mut plan = Self::from_request(request, table, schema_cache)?;
126
127 plan.from = QualifiedIdentifier::unqualified(crate::query::MUTATION_RESULT);
132 plan.from_alias = None;
133 plan.where_clauses.clear();
134
135 for field in plan.select.iter_mut() {
138 if let Some(computed) = field.field.computed.as_mut() {
139 computed.relation = crate::query::MUTATION_RESULT.to_string();
140 }
141 }
142
143 Ok(plan)
144 }
145
146 pub fn has_where(&self) -> bool {
148 !self.where_clauses.is_empty()
149 }
150
151 pub fn has_order(&self) -> bool {
153 !self.order.is_empty()
154 }
155
156 pub fn has_pagination(&self) -> bool {
158 self.range.limit.is_some() || self.range.offset > 0
159 }
160}
161
162fn build_select_fields(items: &[SelectItem], table: &Table) -> Result<Vec<CoercibleSelectField>> {
164 if items.is_empty() {
165 return Ok(table
167 .columns
168 .iter()
169 .map(|(name, col)| CoercibleSelectField::simple(name, &col.data_type))
170 .collect());
171 }
172
173 let mut fields = Vec::new();
174
175 for item in items {
176 match item {
177 SelectItem::Field { field, .. } if field.name == "*" => {
179 fields.extend(
180 table
181 .columns
182 .iter()
183 .map(|(name, col)| CoercibleSelectField::simple(name, &col.data_type)),
184 );
185 }
186 SelectItem::Field {
187 field,
188 aggregate,
189 aggregate_cast,
190 cast,
191 alias,
192 } => {
193 let legacy_count = aggregate.is_none()
199 && field.name == "count"
200 && field.json_path.is_empty()
201 && table.get_column("count").is_none();
202
203 let computed = match table.get_column(&field.name) {
208 Some(_) => None,
209 None => table
213 .get_computed_column(&field.name)
214 .filter(|c| !c.takes_arguments),
215 };
216
217 let pg_type = if field.name.is_empty() || legacy_count {
218 String::new()
219 } else if let Some(computed) = computed {
220 computed.data_type.clone()
221 } else {
222 table
223 .get_column(&field.name)
224 .ok_or_else(|| Error::ColumnNotFound(field.name.clone()))?
225 .data_type
226 .clone()
227 };
228
229 let (field, aggregate) = if legacy_count {
230 (
231 &crate::api_request::Field::simple(""),
232 &Some(crate::api_request::AggregateFunction::Count),
233 )
234 } else {
235 (field, aggregate)
236 };
237
238 let mut resolved = CoercibleField::from_field(field, &pg_type);
239 resolved.computed = computed.map(|computed| crate::plan::ComputedRef {
240 function: computed.function.clone(),
241 relation: table.name.clone(),
242 row_type: table.qualified_identifier(),
243 });
244
245 fields.push(CoercibleSelectField {
246 field: resolved,
247 aggregate: aggregate.clone(),
248 aggregate_cast: aggregate_cast.clone(),
249 cast: cast.clone(),
250 alias: alias.clone(),
251 });
252 }
253 SelectItem::Relation { .. } | SelectItem::SpreadRelation { .. } => {}
255 }
256 }
257
258 Ok(fields)
259}
260
261pub fn resolve_top_level_range(request: &ApiRequest) -> Range {
269 let mut range = request.top_level_range.clone();
270
271 if let Some(from_params) = request.query_params.ranges.get("") {
272 if from_params.limit.is_some() {
273 range.limit = from_params.limit;
274 }
275 if from_params.offset_explicit {
276 range.offset = from_params.offset;
277 }
278 }
279
280 if let Some(max_rows) = request.max_rows {
281 range.limit = Some(match range.limit {
282 Some(requested) => requested.min(max_rows),
283 None => max_rows,
284 });
285 }
286
287 range
288}
289
290fn build_where_clauses(request: &ApiRequest, table: &Table) -> Result<Vec<CoercibleLogicTree>> {
292 let type_resolver = |name: &str| -> String {
296 table
297 .get_column(name)
298 .map(|c| c.nominal_type.clone())
299 .unwrap_or_else(|| "text".to_string())
300 };
301
302 let embedded: std::collections::HashSet<&str> = request
307 .query_params
308 .select
309 .iter()
310 .filter_map(|item| match item {
311 SelectItem::Relation {
312 relation, alias, ..
313 } => Some(alias.as_deref().unwrap_or(relation)),
314 SelectItem::SpreadRelation { relation, .. } => Some(relation.as_str()),
315 SelectItem::Field { .. } => None,
316 })
317 .filter(|name| table.get_column(name).is_none())
318 .collect();
319
320 let mut clauses = Vec::new();
321
322 for filter in &request.query_params.filters_root {
324 if embedded.contains(filter.field.name.as_str()) {
325 continue;
326 }
327 let pg_type = type_resolver(&filter.field.name);
328 clauses.push(CoercibleLogicTree::Stmt(CoercibleFilter::from_filter(
329 filter, &pg_type,
330 )));
331 }
332
333 let embeds = crate::api_request::embedded_names(&request.query_params.select);
339 for (path, tree) in &request.query_params.logic {
340 if path.is_empty() && !tree.names_only(&embeds) {
341 clauses.push(CoercibleLogicTree::from_logic_tree(tree, type_resolver));
342 }
343 }
344
345 for clause in clauses.iter_mut() {
346 attach_computed_tree(clause, table);
347 }
348
349 Ok(clauses)
350}
351
352fn attach_representation(field: &mut CoercibleField, table: &Table, schema_cache: &SchemaCache) {
359 let Some(column) = table.get_column(&field.name) else {
360 return;
361 };
362 if let Some(function) = schema_cache.representation("text", column.representation_type()) {
363 field.transform = Some(function.to_string());
364 }
365}
366
367fn attach_representation_tree(
369 tree: &mut CoercibleLogicTree,
370 table: &Table,
371 schema_cache: &SchemaCache,
372) {
373 match tree {
374 CoercibleLogicTree::Expr { children, .. } => {
375 for child in children {
376 attach_representation_tree(child, table, schema_cache);
377 }
378 }
379 CoercibleLogicTree::Stmt(filter) => {
380 attach_representation(&mut filter.field, table, schema_cache)
381 }
382 CoercibleLogicTree::NullEmbed { .. } => {}
383 }
384}
385
386fn attach_computed(field: &mut CoercibleField, table: &Table) {
393 if table.get_column(&field.name).is_some() {
394 return;
395 }
396 if let Some(computed) = table
397 .get_computed_column(&field.name)
398 .filter(|c| !c.takes_arguments)
399 {
400 field.ir_type = computed.data_type.clone();
401 field.base_type = computed.data_type.clone();
402 field.computed = Some(crate::plan::ComputedRef {
403 function: computed.function.clone(),
404 relation: table.name.clone(),
405 row_type: table.qualified_identifier(),
406 });
407 }
408}
409
410fn attach_computed_tree(tree: &mut CoercibleLogicTree, table: &Table) {
412 match tree {
413 CoercibleLogicTree::Expr { children, .. } => {
414 for child in children {
415 attach_computed_tree(child, table);
416 }
417 }
418 CoercibleLogicTree::Stmt(filter) => attach_computed(&mut filter.field, table),
419 CoercibleLogicTree::NullEmbed { .. } => {}
420 }
421}
422
423fn build_order_terms(request: &ApiRequest, table: &Table) -> Result<Vec<CoercibleOrderTerm>> {
425 let mut terms = Vec::new();
426
427 for (path, order_terms) in &request.query_params.order {
428 if path.is_empty() {
429 for term in order_terms {
430 let field_name = match term {
431 crate::api_request::OrderTerm::Field { field, .. } => &field.name,
432 crate::api_request::OrderTerm::Relation { field, .. } => &field.name,
433 };
434
435 let pg_type = table
436 .get_column(field_name)
437 .map(|c| c.data_type.as_str())
438 .unwrap_or("text");
439
440 let mut resolved = CoercibleOrderTerm::from_order_term(term, pg_type);
441 attach_computed(&mut resolved.field, table);
442 terms.push(resolved);
443 }
444 }
445 }
446
447 Ok(terms)
448}
449
450fn build_relation_selects(
452 items: &[SelectItem],
453 table: &Table,
454 schema_cache: &SchemaCache,
455) -> Result<Vec<RelSelectField>> {
456 let mut rel_selects = Vec::new();
457
458 for item in items {
459 match item {
460 SelectItem::Relation {
461 relation,
462 alias,
463 hint,
464 join_type,
465 select: _,
466 } => {
467 let _rel = schema_cache
469 .find_relationship(
470 &table.qualified_identifier(),
471 relation,
472 hint.as_deref(),
473 &table.schema,
474 )?
475 .ok_or_else(|| {
476 schema_cache.relationship_not_found(
477 &table.qualified_identifier(),
478 relation,
479 hint.as_deref(),
480 &table.schema,
481 )
482 })?;
483
484 rel_selects.push(RelSelectField {
485 name: relation.clone(),
486 agg_alias: alias
487 .clone()
488 .unwrap_or_else(|| format!("pgrst_{}", relation)),
489 join_type: join_type.clone().unwrap_or_default(),
490 is_spread: false,
491 });
492 }
493 SelectItem::SpreadRelation {
494 relation,
495 hint,
496 join_type,
497 select: _,
498 } => {
499 let _rel = schema_cache
500 .find_relationship(
501 &table.qualified_identifier(),
502 relation,
503 hint.as_deref(),
504 &table.schema,
505 )?
506 .ok_or_else(|| {
507 schema_cache.relationship_not_found(
508 &table.qualified_identifier(),
509 relation,
510 hint.as_deref(),
511 &table.schema,
512 )
513 })?;
514
515 rel_selects.push(RelSelectField {
516 name: relation.clone(),
517 agg_alias: format!("pgrst_spread_{}", relation),
518 join_type: join_type.clone().unwrap_or_default(),
519 is_spread: true,
520 });
521 }
522 _ => {}
523 }
524 }
525
526 Ok(rel_selects)
527}
528
529#[derive(Clone, Debug)]
531pub struct ReadPlanTree {
532 pub root: ReadPlan,
534 pub children: Vec<ReadPlanTree>,
536}
537
538impl ReadPlanTree {
539 pub fn empty() -> Self {
541 Self {
542 root: ReadPlan {
543 select: vec![],
544 from: QualifiedIdentifier::unqualified(""),
545 from_alias: None,
546 where_clauses: vec![],
547 order: vec![],
548 range: Range::default(),
549 rel_name: String::new(),
550 rel_to_parent: None,
551 rel_join_conds: vec![],
552 rel_join_type: None,
553 rel_select: vec![],
554 depth: 0,
555 },
556 children: vec![],
557 }
558 }
559
560 pub fn leaf(plan: ReadPlan) -> Self {
562 Self {
563 root: plan,
564 children: vec![],
565 }
566 }
567
568 pub fn add_child(&mut self, child: ReadPlanTree) {
570 self.children.push(child);
571 }
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577
578 #[test]
579 fn test_read_plan_tree_empty() {
580 let tree = ReadPlanTree::empty();
581 assert!(tree.root.select.is_empty());
582 assert!(tree.children.is_empty());
583 }
584}