Skip to main content

postrust_server/
app.rs

1//! Request handling.
2
3use crate::state::AppState;
4use axum::{
5    body::Body,
6    extract::{Request, State},
7    http::StatusCode,
8    response::{IntoResponse, Response},
9};
10use postrust_auth::authenticate;
11use postrust_core::{
12    create_action_plan, parse_request, ActionPlan, ApiRequest, CallPlan, DbActionPlan,
13};
14use postrust_response::{format_response, QueryResult, Response as PgrstResponse};
15use std::sync::Arc;
16use tracing::{debug, error};
17
18/// Column carrying the parent's whole row, for computed relationships.
19///
20/// A computed relationship is a function taking the parent row. By the time an
21/// embed expression runs, the parent is a derived table, and a derived table's
22/// alias has type `record` -- which PostgreSQL will not cast to the table's
23/// composite type. So the row is captured one level down, where the real table
24/// is still in scope and a bare reference to it yields the composite, and the
25/// embed expression reads it from there. It is stripped from the response like
26/// any other column added for embedding.
27///
28/// Defined in `postrust-core` beside the `RETURNING` that emits it for a
29/// mutation, so the two spellings cannot drift.
30use postrust_core::query::PARENT_ROW_COLUMN;
31
32/// Marks an embedded object whose columns belong to its parent.
33///
34/// A spread has no key of its own in the response -- its columns land in the
35/// parent object. In SQL it is still one JSON column like any other embed, so
36/// it is given a name nothing can collide with and dissolved into its parent
37/// once the rows are JSON. Doing it there rather than in SQL is what lets a
38/// spread nest inside another, and lets it spread a computed relationship.
39const SPREAD_KEY_PREFIX: &str = "pgrst_spread_";
40const PARENT_ROW_COLUMN_REF: &str = "\"src\".\"pgrst_parent_row\"";
41
42/// Main request handler.
43pub async fn handle_request(State(state): State<Arc<AppState>>, request: Request) -> Response {
44    let method = request.method().clone();
45    let path = request.uri().path().to_string();
46
47    debug!("{} {}", method, path);
48
49    let verbatim_db_errors = state.config.compat_mode;
50
51    match process_request(state, request).await {
52        Ok(response) => response.into_response(),
53        Err(e) => error_response(e, verbatim_db_errors).into_response(),
54    }
55}
56
57/// Process a request and return a response.
58async fn process_request(
59    state: Arc<AppState>,
60    request: Request,
61) -> Result<Response, postrust_core::Error> {
62    // Extract auth header
63    let auth_header = request
64        .headers()
65        .get("authorization")
66        .and_then(|v| v.to_str().ok());
67
68    // Authenticate, through the token cache where one is configured.
69    let auth_result = match &state.jwt_cache {
70        Some(cache) => cache.authenticate(auth_header, &state.jwt_config),
71        None => authenticate(auth_header, &state.jwt_config),
72    }
73    .map_err(jwt_error)?;
74
75    debug!("Authenticated as role: {}", auth_result.role);
76
77    // Parse request.
78    //
79    // `max_body_size`, not a constant, and not `DefaultBodyLimit` either: that
80    // layer works by setting an extension the body *extractors* read, and this
81    // handler takes the whole `Request` and reads the body itself. The limit
82    // was hardcoded at 10 MiB here, so `PGRST_MAX_BODY_SIZE` was configuring
83    // nothing on this path however it was set.
84    let (parts, body) = request.into_parts();
85    // Still `InvalidBody`, so still a 400: `to_bytes` reports a body that
86    // exceeded the limit and a connection that died mid-body as the same
87    // error, and answering 413 to a truncated upload would be a worse lie than
88    // the 400.
89    let body_bytes = axum::body::to_bytes(body, state.config.max_body_size)
90        .await
91        .map_err(|e| postrust_core::Error::InvalidBody(e.to_string()))?;
92
93    // Build HTTP request for parsing
94    let mut builder = http::Request::builder()
95        .method(parts.method.clone())
96        .uri(parts.uri.clone());
97
98    for (key, value) in &parts.headers {
99        builder = builder.header(key, value);
100    }
101
102    let http_request = builder
103        .body(body_bytes.clone())
104        .map_err(|e| postrust_core::Error::Internal(e.to_string()))?;
105
106    // Parse API request
107    let mut api_request = parse_request(
108        &http_request,
109        state.default_schema(),
110        state.schemas(),
111        state.config.db_max_rows,
112    )?;
113
114    // Parse payload
115    if !body_bytes.is_empty() {
116        let payload = postrust_core::api_request::payload::parse_payload(
117            body_bytes,
118            &api_request.content_media_type,
119        )?;
120        api_request.payload = payload;
121    }
122
123    // A body that names different columns row by row cannot be written by one
124    // statement. Skipped when `?columns=` says which columns to write, since
125    // then the rows are free to differ.
126    if api_request.query_params.columns.is_none() {
127        if let Some(payload) = &api_request.payload {
128            postrust_core::api_request::payload::validate_uniform_keys(payload)?;
129        }
130    }
131
132    // A write with nothing to write is refused here rather than left to
133    // produce an `UPDATE ... SET` with no assignments, which PostgreSQL
134    // reports as a syntax error -- true, and no use at all to the client.
135    if api_request.payload.is_none() && needs_payload(&api_request) {
136        return Err(postrust_core::Error::InvalidBody(
137            "Empty or invalid json".into(),
138        ));
139    }
140
141    // `application/vnd.pgrst.plan` asks for the query plan instead of the
142    // result. It is off unless a server turns it on -- it says a great deal
143    // about the schema and the data -- and a server that has not is required
144    // to say so rather than answer with something else.
145    //
146    // Checked after the body, not before it: a request that could not be read
147    // at all has not got as far as asking for anything, and PostgREST answers
148    // it with what is wrong with the request rather than with what the server
149    // declines to serve.
150    if let Some(plan) = requested_plan(&api_request) {
151        return Err(postrust_core::Error::InvalidMediaType(plan));
152    }
153
154    // Get schema cache
155    let schema_cache = state.schema_cache().await;
156
157    // Embedding joins on a column of the parent row, so that column has to be
158    // selected even when the client did not ask for it. Any column added this
159    // way is removed from the response once the embed is attached.
160    let added_join_columns = add_embed_join_columns(&mut api_request, &schema_cache)?;
161
162    if !state.config.db_aggregates_enabled && selects_an_aggregate(&api_request) {
163        return Err(postrust_core::Error::AggregatesNotAllowed);
164    }
165
166    // Create execution plan
167    let plan = create_action_plan(&api_request, &schema_cache)?;
168
169    // Execute plan
170    let result = execute_plan(
171        &state,
172        &api_request,
173        &plan,
174        &auth_result,
175        &added_join_columns,
176    )
177    .await?;
178
179    // Format response
180    // A singular response the result cannot satisfy is the client's business,
181    // not an internal failure: it asked for one object and the query answered
182    // with some other number of rows.
183    let response = format_response(&api_request, &result).map_err(|e| match e {
184        postrust_response::FormatError::MultipleRows | postrust_response::FormatError::NotFound => {
185            // How many rows there were, not how many were kept: a write the
186            // caller wanted no representation from still touched them, and
187            // that count is what the client got wrong.
188            postrust_core::Error::NotSingular {
189                rows: result.rows.len().max(result.affected),
190            }
191        }
192        other => postrust_core::Error::Internal(other.to_string()),
193    })?;
194
195    Ok(build_response(response))
196}
197
198/// The plan media type a request asked for, when that is all it will accept.
199///
200/// A request that would also take JSON gets JSON; one that will take only the
201/// plan is refused, and the message names what it asked for.
202fn requested_plan(api_request: &ApiRequest) -> Option<String> {
203    use postrust_core::api_request::MediaType;
204
205    // Named back with their parameters, since that is what was asked for: a
206    // plan for CSV and a plan for JSON are different requests, and a client
207    // told only that "application/vnd.pgrst.plan+json" is unavailable has not
208    // been told which of its requests was refused.
209    let accepted: Vec<String> = api_request
210        .accept_media_types
211        .iter()
212        .filter(|media| matches!(media, MediaType::Plan { .. }))
213        .map(MediaType::to_mime)
214        .collect();
215
216    match accepted.len() == api_request.accept_media_types.len() && !accepted.is_empty() {
217        true => Some(accepted.join(", ")),
218        false => None,
219    }
220}
221
222/// Which kind of mutation this request is, if it is one.
223fn mutation_kind(api_request: &ApiRequest) -> Option<postrust_core::api_request::Mutation> {
224    use postrust_core::api_request::{Action, DbAction};
225
226    match &api_request.action {
227        Action::Db(DbAction::RelationMut { mutation, .. }) => Some(mutation.clone()),
228        _ => None,
229    }
230}
231
232/// Split a leading `WITH ... AS (...)` off the front of a statement.
233///
234/// Returns the clause -- with its trailing space, or empty when there is none
235/// -- and the statement that follows it. A `WITH` containing an `INSERT`,
236/// `UPDATE` or `DELETE` may only appear at the top level, so anything that
237/// wraps the statement has to wrap what follows the clause and put the clause
238/// back in front.
239fn split_leading_cte(sql: &str) -> (String, &str) {
240    let Some(open) = sql.strip_prefix("WITH ").and_then(|rest| rest.find('(')) else {
241        return (String::new(), sql);
242    };
243    let open = open + "WITH ".len();
244
245    let mut depth = 0usize;
246    let mut quoted = None::<char>;
247    for (index, ch) in sql[open..].char_indices() {
248        match (quoted, ch) {
249            (Some(quote), ch) if ch == quote => quoted = None,
250            (Some(_), _) => {}
251            (None, '\'') | (None, '"') => quoted = Some(ch),
252            (None, '(') => depth += 1,
253            (None, ')') => {
254                depth -= 1;
255                if depth == 0 {
256                    let end = open + index + 1;
257                    return (format!("{} ", &sql[..end]), sql[end..].trim_start());
258                }
259            }
260            _ => {}
261        }
262    }
263
264    (String::new(), sql)
265}
266
267/// Render a map of strings as a JSON object.
268///
269/// PostgreSQL will not accept every header name as a setting name -- a `-` is
270/// not allowed in one from version 14 -- so the whole map goes into a single
271/// setting as JSON and a function picks out what it wants with `->>`.
272fn json_object<'a>(entries: impl IntoIterator<Item = (&'a String, &'a String)>) -> String {
273    serde_json::Value::Object(
274        entries
275            .into_iter()
276            .map(|(name, value)| (name.clone(), serde_json::Value::String(value.clone())))
277            .collect(),
278    )
279    .to_string()
280}
281
282/// The prefix given to primary key columns added only to build `Location`.
283const LOCATION_KEY_PREFIX: &str = "pgrst_location_";
284
285/// The column a media handler's statement carries its row count in.
286///
287/// Named like the other bookkeeping columns so it cannot collide with anything
288/// the request selected. Never reaches the client: the response body is the
289/// rendered value, read from the first column.
290const MEDIA_ROW_COUNT_COLUMN: &str = "pgrst_media_rows";
291
292/// Whether this request writes the row its URL names.
293fn is_upsert(api_request: &ApiRequest) -> bool {
294    use postrust_core::api_request::{Action, DbAction, Mutation};
295
296    matches!(
297        &api_request.action,
298        Action::Db(DbAction::RelationMut {
299            mutation: Mutation::SingleUpsert,
300            ..
301        })
302    )
303}
304
305/// The `Location` of a newly created row, and removal of the key columns that
306/// were added to find it.
307///
308/// `/projects?id=eq.7` -- the address the row can be read back from, which is
309/// what a 201 is required to give. A key column that came back null is
310/// addressed with `is.null`, since `eq.` would match nothing.
311fn build_location(
312    api_request: &ApiRequest,
313    rows: &mut [serde_json::Value],
314    keys: &[String],
315) -> Option<String> {
316    if keys.is_empty() {
317        return None;
318    }
319
320    let table = api_request.path.rsplit('/').next()?;
321    let mut conditions = Vec::with_capacity(keys.len());
322
323    for key in keys {
324        let value = rows
325            .first()?
326            .get(key)
327            .or_else(|| rows.first()?.get(format!("{}{}", LOCATION_KEY_PREFIX, key)))?;
328        conditions.push(match value {
329            serde_json::Value::Null => format!("{}=is.null", urlencode(key)),
330            serde_json::Value::String(text) => {
331                format!("{}=eq.{}", urlencode(key), urlencode(text))
332            }
333            other => format!("{}=eq.{}", urlencode(key), urlencode(&other.to_string())),
334        });
335    }
336
337    for row in rows.iter_mut() {
338        if let Some(object) = row.as_object_mut() {
339            for key in keys {
340                object.remove(&format!("{}{}", LOCATION_KEY_PREFIX, key));
341            }
342        }
343    }
344
345    Some(format!("/{}?{}", table, conditions.join("&")))
346}
347
348/// Percent-encode everything a query-string value may not carry literally.
349fn urlencode(value: &str) -> String {
350    let mut encoded = String::with_capacity(value.len());
351    for byte in value.as_bytes() {
352        match byte {
353            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
354                encoded.push(*byte as char)
355            }
356            other => encoded.push_str(&format!("%{:02X}", other)),
357        }
358    }
359    encoded
360}
361
362/// Whether this request must carry a body.
363///
364/// Insert, update and upsert all write values that can only come from one.
365/// A delete names its rows in the query string, and a function call may take
366/// no arguments at all.
367fn needs_payload(api_request: &ApiRequest) -> bool {
368    use postrust_core::api_request::{Action, DbAction, Mutation};
369
370    matches!(
371        &api_request.action,
372        Action::Db(DbAction::RelationMut {
373            mutation: Mutation::Create | Mutation::Update | Mutation::SingleUpsert,
374            ..
375        })
376    )
377}
378
379/// Classify a JWT failure the way PostgREST reports it.
380///
381/// A token that could not be read at all and one that was read and found
382/// wanting are different answers to the client: the first says the credential
383/// is unusable, the second says what about it was unacceptable. PostgREST
384/// gives them different codes, and clients branch on them.
385fn jwt_error(error: postrust_auth::JwtError) -> postrust_core::Error {
386    use postrust_auth::JwtError;
387
388    match error {
389        JwtError::NoIdentity => postrust_core::Error::MissingAuth,
390        JwtError::SecretMissing => postrust_core::Error::JwtSecretMissing,
391        // Read, and found wanting: the token itself is intact, and what it
392        // claims is what was not accepted.
393        JwtError::Claim(fault) => postrust_core::Error::JwtClaim(fault.to_string()),
394        JwtError::InvalidSignature => {
395            postrust_core::Error::InvalidJwt("JWT cryptographic operation failed".into())
396        }
397        JwtError::InvalidHeaderFormat => {
398            postrust_core::Error::InvalidJwt("Unsupported token type".into())
399        }
400        JwtError::NoSuitableKey => postrust_core::Error::NoSuitableJwtKey,
401    }
402}
403
404/// Execute an action plan.
405async fn execute_plan(
406    state: &AppState,
407    api_request: &ApiRequest,
408    plan: &ActionPlan,
409    auth: &postrust_auth::AuthResult,
410    added_join_columns: &[String],
411) -> Result<QueryResult, postrust_core::Error> {
412    match plan {
413        ActionPlan::Db(db_plan) => {
414            // A media type the schema renders itself replaces the whole
415            // response body: the aggregate is applied over the rows the
416            // request selected, so it sees exactly what would otherwise have
417            // been serialised as JSON.
418            let media_handler = {
419                let schema_cache = state.schema_cache().await;
420                read_target(api_request, Some(db_plan), &schema_cache).and_then(|qi| {
421                    api_request.accept_media_types.iter().find_map(|media| {
422                        schema_cache
423                            .media_handler(
424                                &api_request.schema,
425                                media.content_type(),
426                                &qi,
427                                has_default_select(&api_request.query_params.select),
428                            )
429                            .map(|(handler, resolved)| {
430                                (
431                                    resolved.to_string(),
432                                    format!(
433                                        "{}.{}",
434                                        postrust_sql::escape_ident(&handler.aggregate.schema),
435                                        postrust_sql::escape_ident(&handler.aggregate.name)
436                                    ),
437                                    // Applied to the table's own row, not to
438                                    // the derived one, whenever the derived
439                                    // row would be a worse description of it.
440                                    // A handler naming a table needs the row
441                                    // type by name; one taking `anyelement`
442                                    // needs the columns to still *be* what
443                                    // they are -- a handler calling
444                                    // `ST_AsGeoJSON` cannot find a geometry in
445                                    // a row whose geometry this side already
446                                    // rendered to jsonb. Only where the
447                                    // request did not reshape the rows, since
448                                    // then the two are the same rows.
449                                    handler.table.is_some()
450                                        || has_default_select(&api_request.query_params.select),
451                                    handler.base_type.clone(),
452                                )
453                            })
454                    })
455                })
456            };
457
458            // Content negotiation. A media type is rendered by a builtin, by a
459            // handler the schema declared, or by the function's own return
460            // type -- and a request that names only types nothing here can
461            // render has not been answered by serving it JSON under a
462            // `Content-Type` it never asked for. PostgREST refuses it, naming
463            // everything that was asked for.
464            if media_handler.is_none()
465                && declared_media_type(db_plan, api_request).is_none()
466                && !api_request
467                    .accept_media_types
468                    .iter()
469                    .any(renders_without_a_handler)
470            {
471                return Err(postrust_core::Error::InvalidMediaType(
472                    api_request
473                        .accept_media_types
474                        .iter()
475                        .map(postrust_core::api_request::MediaType::to_mime)
476                        .collect::<Vec<_>>()
477                        .join(", "),
478                ));
479            }
480
481            // Carry the parent's whole row out of the inner query when a
482            // computed relationship needs it as an argument. It has to be
483            // taken here, where the real table is still in scope: a bare
484            // reference to the table yields its composite type, whereas the
485            // derived table it becomes one level up yields a `record`.
486            let needs_parent_row = added_join_columns.iter().any(|c| c == PARENT_ROW_COLUMN)
487                || matches!(&media_handler, Some((_, _, true, _)));
488            let db_plan = &if needs_parent_row {
489                let mut adjusted = db_plan.clone();
490                // A function's result is a relation too, so the row is
491                // reachable there by exactly the same means -- which is what
492                // makes a computed relationship work on
493                // `/rpc/getallvideogames` and not only on a table.
494                //
495                // Named after the table it returns, not after the function.
496                // The builder aliases the call to that table (`FROM
497                // test.getallvideogames() AS videogames`), because a computed
498                // *column* has to be able to name the row it is a function of.
499                // Naming the function here instead asks for a relation that is
500                // no longer in scope, and the request that used to work --
501                // `?select=name,designer:computed_designers(name)` -- answers
502                // `column "getallvideogames" does not exist`.
503                let (tree, relation) = match &mut adjusted {
504                    DbActionPlan::Read(tree) => {
505                        let relation = tree.root.from.name.clone();
506                        (Some(tree), relation)
507                    }
508                    DbActionPlan::Call { read, .. } => {
509                        let relation = read
510                            .as_ref()
511                            .map(|tree| tree.root.from.name.clone())
512                            .unwrap_or_default();
513                        (read.as_mut(), relation)
514                    }
515                    // The row is taken inside the statement, by `RETURNING`,
516                    // and read back off the CTE as an ordinary column -- so
517                    // unlike the two above there is no relation to name here,
518                    // and the read tree selects the column rather than a row.
519                    DbActionPlan::MutateRead { mutate, read } => {
520                        let returning = match mutate {
521                            postrust_core::plan::MutatePlan::Insert { returning, .. }
522                            | postrust_core::plan::MutatePlan::Update { returning, .. }
523                            | postrust_core::plan::MutatePlan::Delete { returning, .. } => {
524                                returning
525                            }
526                        };
527                        if !returning.iter().any(|c| c == PARENT_ROW_COLUMN) {
528                            returning.push(PARENT_ROW_COLUMN.to_string());
529                        }
530                        // A mutation that returns no body has no read tree,
531                        // and nothing to embed into either.
532                        if let Some(read) = read.as_mut() {
533                            if !read.root.select.iter().any(|field| {
534                                field.alias.as_deref() == Some(PARENT_ROW_COLUMN)
535                                    || field.field.name == PARENT_ROW_COLUMN
536                            }) {
537                                read.root.select.push(
538                                    postrust_core::plan::CoercibleSelectField::simple(
539                                        PARENT_ROW_COLUMN,
540                                        "",
541                                    ),
542                                );
543                            }
544                        }
545                        (None, String::new())
546                    }
547                };
548
549                if let Some(tree) = tree {
550                    // An empty select means every column; naming the row would
551                    // otherwise replace them rather than join them.
552                    if tree.root.select.is_empty() {
553                        tree.root
554                            .select
555                            .push(postrust_core::plan::CoercibleSelectField::simple("*", ""));
556                    }
557
558                    let mut field =
559                        postrust_core::plan::CoercibleField::simple(relation, String::new());
560                    field.full_row = true;
561                    tree.root
562                        .select
563                        .push(postrust_core::plan::CoercibleSelectField {
564                            field,
565                            aggregate: None,
566                            aggregate_cast: None,
567                            cast: None,
568                            alias: Some(PARENT_ROW_COLUMN.to_string()),
569                        });
570                }
571                adjusted
572            } else {
573                db_plan.clone()
574            };
575
576            // The `Location` of a created row is built from its primary key,
577            // which the client may not have selected. The columns are added
578            // under names of our own so they cannot collide with anything the
579            // request asked for, and are taken back out of the response once
580            // the header is built.
581            //
582            // Only for `Prefer: return=headers-only`, which is the request
583            // that has no other way to learn where the row went. A client
584            // given the row back can read the key out of it, and PostgREST
585            // sends no `Location` there -- so neither does this. Deciding it
586            // here also means the key columns are never added to a select
587            // that had no use for them.
588            let location_keys: Vec<String> = match db_plan {
589                DbActionPlan::MutateRead {
590                    mutate: postrust_core::plan::MutatePlan::Insert { pk_cols, .. },
591                    ..
592                } if !is_upsert(api_request) && wants_location(api_request) => pk_cols.clone(),
593                _ => Vec::new(),
594            };
595            // Whether the write created a row or merged into an existing one.
596            // The statement is the only place that can say, so the answer is
597            // carried out of it as a column and taken back out of the response.
598            let reports_inserted = matches!(
599                db_plan,
600                DbActionPlan::MutateRead {
601                    mutate: postrust_core::plan::MutatePlan::Insert {
602                        on_conflict: Some(_),
603                        reports_inserted: true,
604                        ..
605                    },
606                    ..
607                }
608            );
609
610            let db_plan = &if location_keys.is_empty() && !reports_inserted {
611                db_plan.clone()
612            } else {
613                let mut adjusted = db_plan.clone();
614                if let DbActionPlan::MutateRead {
615                    read: Some(tree), ..
616                } = &mut adjusted
617                {
618                    for key in &location_keys {
619                        tree.root.select.push(
620                            postrust_core::plan::CoercibleSelectField::with_alias(
621                                key,
622                                "",
623                                &format!("{}{}", LOCATION_KEY_PREFIX, key),
624                            ),
625                        );
626                    }
627                    if reports_inserted {
628                        tree.root
629                            .select
630                            .push(postrust_core::plan::CoercibleSelectField::simple(
631                                postrust_core::query::INSERTED_COLUMN,
632                                "boolean",
633                            ));
634                    }
635                }
636                adjusted
637            };
638
639            // Build SQL
640            let query = postrust_core::query::build_query(
641                &ActionPlan::Db(db_plan.clone()),
642                Some(&auth.role),
643            )?;
644
645            if !query.has_main() {
646                return Ok(QueryResult::default());
647            }
648
649            let (mut sql, params) = query.build_main();
650
651            // A `Prefer: count` needs the same query without its page: the
652            // total is what the filters match, not what this page returned.
653            // LIMIT and OFFSET render as literals, so dropping them leaves the
654            // placeholders and their numbering untouched and the count query
655            // can be bound with the very same parameters.
656            let mut count_sql = match api_request.preferences.count {
657                Some(_) => unpaged_sql(db_plan, &auth.role)?,
658                None => None,
659            };
660
661            // Embed related resources in the same query.
662            //
663            // Each relation becomes a correlated subselect in the SELECT list,
664            // so PostgreSQL builds the nested JSON while it already has the
665            // parent row -- one round trip for the whole tree instead of one per
666            // relation per level, and no grouping or attaching in this process.
667            //
668            // Parent columns stay ordinary typed columns, so they are converted
669            // to JSON by the same code as a request without embeds and a NUMERIC
670            // or timestamp renders identically either way.
671            let mut embed_filters = EmbedFilters {
672                filters: &api_request.query_params.filters,
673                orders: &api_request.query_params.order,
674                ranges: &api_request.query_params.ranges,
675                logic: &api_request.query_params.logic,
676                params: Vec::new(),
677                base: params.len(),
678                max_rows: api_request.max_rows,
679                alias_counter: 0,
680                // A schema-declared handler produces the whole body from
681                // these rows, so each column reaches the client as *text* --
682                // and that is the one place the cast is observable. Keyed on
683                // whether there is a handler at all, not on which row shape it
684                // wants: `{"id":1}` against PostgREST's `{"id": 1}` is the
685                // difference either way.
686                rendering: match &media_handler {
687                    Some(_) => postrust_core::EmbedRendering::Normalised,
688                    None => postrust_core::EmbedRendering::Verbatim,
689                },
690            };
691
692            // The single-query form cannot express a spread, whose columns have
693            // to land in the parent object rather than under a key of their
694            // own. One anywhere in the tree sends the whole request down the
695            // two-query path, which can: taking the single-query path for the
696            // plain relations would embed those and drop the spread, since the
697            // two-query path only runs when the first produced nothing.
698            let embed_parent = {
699                let schema_cache = state.schema_cache().await;
700                read_target(api_request, Some(db_plan), &schema_cache)
701            };
702            let embed_level = match embed_parent.clone() {
703                Some(parent_qi)
704                    if api_request.query_params.select.iter().any(|item| {
705                        matches!(
706                            item,
707                            postrust_core::api_request::SelectItem::Relation { .. }
708                                | postrust_core::api_request::SelectItem::SpreadRelation { .. }
709                        )
710                    }) =>
711                {
712                    let schema_cache = state.schema_cache().await;
713                    // A computed relationship takes the parent's row, which
714                    // now reaches here on every path: a read names the table,
715                    // and a mutation returns the row out of the statement
716                    // where it still has the table's type rather than the
717                    // CTE's anonymous one.
718                    let parent_row = PARENT_ROW_COLUMN_REF;
719                    build_embed_expressions(
720                        &schema_cache,
721                        &parent_qi,
722                        "src",
723                        parent_row,
724                        &api_request.query_params.select,
725                        &mut embed_filters,
726                        &[],
727                        &[],
728                    )?
729                }
730                _ => EmbedLevel::default(),
731            };
732            let mut embed_level = embed_level;
733
734            // Ordering by an embedded column is resolved here rather than in
735            // the read plan: the plan has no way to reach another table.
736            if let (Some(parent_qi), DbActionPlan::Read(tree)) = (&embed_parent, db_plan) {
737                let schema_cache = state.schema_cache().await;
738                embed_level.orders = build_embed_orders(
739                    &schema_cache,
740                    parent_qi,
741                    &tree.root.order,
742                    &api_request.query_params.select,
743                    &mut embed_filters,
744                )?;
745            }
746
747            // Filter parameters are appended in the order the predicates were
748            // renumbered, so placeholder N in the wrapped SQL lines up with
749            // params[N - 1].
750            let mut params = params;
751            params.extend(embed_filters.params);
752
753            // Built whenever there were relations at all, not only when they
754            // produced expressions: `clients!inner()` contributes no column
755            // but does narrow the parent, and its predicate binds parameters
756            // that have already been appended -- left unapplied, the query is
757            // sent with more parameters than it uses.
758            if embed_level.saw_relations {
759                // `!inner` removes parent rows, so it has to be applied before
760                // the page is taken. Left where it is, the inner query's
761                // LIMIT would run first and the join would then trim an
762                // already-truncated page -- a request for one row could come
763                // back empty. So the inner query is rebuilt unpaged and the
764                // range moves out to the wrapper, after the join.
765                //
766                // Rebuilding is safe for parameter numbering: LIMIT and OFFSET
767                // render as literals, so dropping them leaves the placeholders
768                // and their count untouched.
769                //
770                // Ordering by an embedded resource's column needs the same
771                // treatment for the same reason: the value being ordered on is
772                // only reachable out here, so the page cannot be taken before
773                // it is known.
774                let orders_by_embed = matches!(db_plan, DbActionPlan::Read(tree)
775                    if tree.root.order.iter().any(|t| t.relation.is_some()));
776
777                let mut page = None;
778                if !embed_level.inner_joins.is_empty() || orders_by_embed {
779                    if let DbActionPlan::Read(tree) = db_plan {
780                        let mut unpaged = tree.clone();
781                        page = Some(std::mem::take(&mut unpaged.root.range));
782                        sql = postrust_core::query::build_query(
783                            &ActionPlan::Db(DbActionPlan::Read(unpaged)),
784                            Some(&auth.role),
785                        )?
786                        .build_main()
787                        .0;
788                    }
789                }
790
791                let mut projection = String::from("src.*");
792                for (field_name, expression) in &embed_level.expressions {
793                    projection.push_str(", ");
794                    projection.push_str(expression);
795                    projection.push_str(" AS ");
796                    projection.push_str(&postrust_sql::escape_ident(field_name));
797                }
798                // A data-modifying `WITH` has to stay at the top level:
799                // PostgreSQL refuses one nested inside a subquery, which is
800                // where wrapping the whole statement would put it. The clause
801                // is lifted out and put back in front of the wrapper.
802                let inner_sql = std::mem::take(&mut sql);
803                let (with_clause, inner_sql) = split_leading_cte(&inner_sql);
804                sql = format!(
805                    "{}SELECT {} FROM ({}) AS src",
806                    with_clause, projection, inner_sql
807                );
808
809                // `?clients=is.null` asks whether the embed matched anything,
810                // not about a column. The embed's expression is the thing to
811                // test, and it is only in scope out here -- referring to the
812                // name it is given would be referring to a column of the very
813                // select that defines it.
814                let mut predicates = embed_level.inner_joins.clone();
815                for filter in &api_request.query_params.filters_root {
816                    let Some((_, exists)) = embed_level
817                        .filterable
818                        .iter()
819                        .find(|(name, _)| name == &filter.field.name)
820                    else {
821                        continue;
822                    };
823                    let negated = match &filter.op_expr.operation {
824                        postrust_core::api_request::Operation::Is(
825                            postrust_core::api_request::IsValue::Null,
826                        ) => filter.op_expr.negated,
827                        postrust_core::api_request::Operation::Is(
828                            postrust_core::api_request::IsValue::NotNull,
829                        ) => !filter.op_expr.negated,
830                        _ => continue,
831                    };
832                    // `is.null` asks for parents the embed did not match, so
833                    // the existence test is the negation of it.
834                    predicates.push(match negated {
835                        true => exists.clone(),
836                        false => format!("NOT {}", exists),
837                    });
838                }
839
840                // `or=(clientinfo.not.is.null,contact.not.is.null)` asks the
841                // same question of several embeds at once, and the answer is
842                // one predicate combining their existence tests. The read
843                // plan left the tree alone precisely so that it could be
844                // built here, where the embeds are in scope.
845                let embeds =
846                    postrust_core::api_request::embedded_names(&api_request.query_params.select);
847                for (path, tree) in &api_request.query_params.logic {
848                    if !path.is_empty() || !tree.names_only(&embeds) {
849                        continue;
850                    }
851                    if let Some(predicate) = embed_logic_predicate(tree, &embed_level.filterable) {
852                        predicates.push(predicate);
853                    }
854                }
855
856                if !predicates.is_empty() {
857                    sql.push_str(" WHERE ");
858                    sql.push_str(&predicates.join(" AND "));
859                }
860
861                if !embed_level.orders.is_empty() {
862                    sql.push_str(" ORDER BY ");
863                    sql.push_str(&embed_level.orders.join(", "));
864                }
865
866                // `!inner` and `?rel=is.null` decide which parent rows survive,
867                // so the count has to be taken after them -- the same wrapper,
868                // over the unpaged query.
869                count_sql = count_sql.map(|base| {
870                    let (with_clause, base) = split_leading_cte(&base);
871                    let mut counted = format!(
872                        "{}SELECT {} FROM ({}) AS src",
873                        with_clause, projection, base
874                    );
875                    if !predicates.is_empty() {
876                        counted.push_str(" WHERE ");
877                        counted.push_str(&predicates.join(" AND "));
878                    }
879                    counted
880                });
881
882                if let Some(range) = page {
883                    if let Some(limit) = range.limit {
884                        sql.push_str(&format!(" LIMIT {}", limit));
885                    }
886                    if range.offset > 0 {
887                        sql.push_str(&format!(" OFFSET {}", range.offset));
888                    }
889                }
890            }
891
892            // `application/geo+json` has a rendering of its own even where no
893            // schema declares one: a FeatureCollection whose geometry is the
894            // table's geometry column and whose properties are the rest. A
895            // schema-declared handler overrides it, which is why this only
896            // runs when none matched.
897            //
898            // Asking for GeoJSON from rows that carry no geometry is an error
899            // rather than a plain JSON body: the client asked for features,
900            // and a list of objects with no geometry is not features.
901            let wants_geojson = media_handler.is_none()
902                && api_request
903                    .accept_media_types
904                    .iter()
905                    .any(|m| matches!(m, postrust_core::api_request::MediaType::GeoJson));
906            let geojson_column = if wants_geojson {
907                let schema_cache = state.schema_cache().await;
908                let target = read_target(api_request, Some(db_plan), &schema_cache);
909                match target.as_ref().and_then(|qi| schema_cache.get_table(qi)) {
910                    Some(table) => match geometry_key(table, &api_request.query_params.select) {
911                        Some(column) => Some(column),
912                        None => return Err(postrust_core::Error::MissingGeometryColumn),
913                    },
914                    // Nothing this side can name a geometry column on -- a
915                    // scalar function, say. Left to the database, which is
916                    // where the answer was coming from anyway.
917                    None => None,
918                }
919            } else {
920                None
921            };
922
923            if let Some((media_type, aggregate, over_row, _)) = &media_handler {
924                // The aggregate takes the table's own row type. A derived
925                // table yields `record`, which will not cast to it, so where
926                // the handler names a table the aggregate is applied to the
927                // column carrying the real row -- the same column a computed
928                // relationship reads. A handler taking `anyelement` has no
929                // such requirement and takes the derived row directly.
930                let argument = if *over_row {
931                    format!(
932                        "pgrst_media.{}",
933                        postrust_sql::escape_ident(PARENT_ROW_COLUMN)
934                    )
935                } else {
936                    "pgrst_media".to_string()
937                };
938                // `count(*)` alongside the handler: both are aggregates over
939                // the same set, and the rendered body is one value that has
940                // forgotten how many rows went into it. `Content-Range`
941                // describes the row set rather than the rendering, so the
942                // count has to be carried out of the same statement -- asking
943                // again afterwards would be a second snapshot.
944                let (with_clause, inner) = split_leading_cte(&sql);
945                sql = format!(
946                    "{}SELECT {}({})::text AS pgrst_body, count(*) AS {} \
947                     FROM ({}) pgrst_media",
948                    with_clause, aggregate, argument, MEDIA_ROW_COUNT_COLUMN, inner
949                );
950                debug!("Rendering {} via {}", media_type, aggregate);
951            }
952
953            debug!("Executing SQL: {}", sql);
954            debug!("With {} parameters", params.len());
955
956            // Everything for this request runs in one transaction.
957            //
958            // `SET LOCAL ROLE` and `set_config(..., true)` are scoped to a
959            // transaction. Sent on a bare pooled connection they apply to their
960            // own implicit single-statement transaction and are gone before the
961            // next statement runs, so the query executed as the pool's login
962            // role with no JWT claims set -- row-level security and role grants
963            // were not being applied at all. PostgreSQL says so in its log:
964            // "SET LOCAL can only be used in transaction blocks".
965            //
966            // Embedding runs on this same transaction, which also means a
967            // parent row and its children come from one snapshot rather than
968            // from two separate reads.
969            let mut tx = state
970                .pool
971                .begin()
972                .await
973                .map_err(|e| postrust_core::Error::ConnectionPool(e.to_string()))?;
974
975            // Isolation level. `SET TRANSACTION` is only accepted before the
976            // first statement of a transaction, so it goes here.
977            //
978            // A role's own setting wins over the server-wide
979            // `PGRST_DB_TX_ISOLATION`, and both are closed enums rendered by
980            // `to_sql` -- never caller text. Issued only when it differs from
981            // PostgreSQL's own default, so the common case costs no round trip.
982            let isolation = state
983                .config
984                .role_settings
985                .get(&auth.role)
986                .and_then(|r| r.isolation_level.as_ref())
987                .unwrap_or(&state.config.db_tx_isolation);
988            if *isolation != postrust_core::IsolationLevel::ReadCommitted {
989                sqlx::query(&format!(
990                    "SET TRANSACTION ISOLATION LEVEL {}",
991                    isolation.to_sql()
992                ))
993                .execute(&mut *tx)
994                .await
995                .map_err(map_sqlx_error)?;
996            }
997
998            // A read request runs in a read-only transaction.
999            //
1000            // Without this a GET can change data: `GET /rpc/some_volatile_fn`
1001            // happily executes whatever the function does. PostgreSQL is the
1002            // right place to enforce it, since it covers anything reachable
1003            // from the statement -- a volatile function, a trigger, a rule --
1004            // rather than only what the planner thought to look at.
1005            if is_read_only(api_request) {
1006                sqlx::query("SET TRANSACTION READ ONLY")
1007                    .execute(&mut *tx)
1008                    .await
1009                    .map_err(map_sqlx_error)?;
1010            }
1011
1012            // Everything the request tells the database about itself, in
1013            // one statement.
1014            //
1015            // These are `SET LOCAL` by another spelling -- `set_config(k, v,
1016            // true)` -- and a function or an RLS policy reads them back with
1017            // `current_setting`. Sent as one `SELECT` rather than one
1018            // statement each, which is a round trip per setting otherwise, and
1019            // there are eight of them before a request has done any work.
1020            //
1021            // The order is PostgREST's: the settings a role carries, then the
1022            // role, then everything about the request. The role goes on after
1023            // its own settings so that a `GRANT SET ON PARAMETER` on the
1024            // authenticator still applies when they are set.
1025            {
1026                let mut path = vec![api_request.schema.clone()];
1027                path.extend(state.config.db_extra_search_path.iter().cloned());
1028                let search_path = path
1029                    .iter()
1030                    .map(|s| postrust_sql::escape_ident(s))
1031                    .collect::<Vec<_>>()
1032                    .join(", ");
1033
1034                let mut settings: Vec<(String, String)> = Vec::new();
1035
1036                // The settings a role carries come first, before the `role`
1037                // that follows them -- see the note above on
1038                // `GRANT SET ON PARAMETER`.
1039                if let Some(role_settings) = state.config.role_settings.get(&auth.role) {
1040                    if let Some(timeout) = role_settings.statement_timeout {
1041                        settings.push(("statement_timeout".to_string(), timeout.to_string()));
1042                    }
1043                }
1044
1045                settings.extend([
1046                    ("search_path".to_string(), search_path),
1047                    ("role".to_string(), auth.role.clone()),
1048                    ("request.method".to_string(), api_request.method.clone()),
1049                    ("request.path".to_string(), api_request.path.clone()),
1050                    (
1051                        "request.headers".to_string(),
1052                        json_object(&api_request.headers),
1053                    ),
1054                    (
1055                        "request.cookies".to_string(),
1056                        json_object(&api_request.cookies),
1057                    ),
1058                ]);
1059                settings.extend(auth.claim_settings());
1060
1061                // `app_settings`, exposed as PostgREST spells them: a function
1062                // or policy reads one back with
1063                // `current_setting('app.settings.<key>')`.
1064                for (key, value) in &state.config.app_settings {
1065                    settings.push((format!("app.settings.{key}"), value.clone()));
1066                }
1067
1068                // `Prefer: timezone` is applied to the session, so every value
1069                // the database renders -- and every `now()` a function calls --
1070                // sees it. An unknown zone is PostgreSQL's to reject, and it
1071                // does, with a message naming the value the client sent.
1072                if let Some(timezone) = &api_request.preferences.timezone {
1073                    settings.push(("timezone".to_string(), timezone.clone()));
1074                }
1075
1076                let calls = (0..settings.len())
1077                    .map(|i| format!("set_config(${}, ${}, true)", i * 2 + 1, i * 2 + 2))
1078                    .collect::<Vec<_>>()
1079                    .join(", ");
1080                let sql = format!("SELECT {}", calls);
1081                let mut query = sqlx::query(&sql);
1082                for (name, value) in &settings {
1083                    query = query.bind(name).bind(value);
1084                }
1085                query.execute(&mut *tx).await.map_err(map_sqlx_error)?;
1086            }
1087
1088            // `db_pre_request`: a function called before the request's own
1089            // query, on the same transaction, so it sees every setting above
1090            // and anything it raises aborts the request. PostgREST's use for
1091            // it is a custom authorisation check that `RAISE`s to refuse.
1092            //
1093            // The name is an identifier from the configuration, not from the
1094            // request, and is escaped as one -- a schema-qualified name stays
1095            // two identifiers so `auth.check` does not become `"auth.check"`.
1096            if let Some(pre_request) = &state.config.db_pre_request {
1097                let qualified = pre_request
1098                    .split('.')
1099                    .map(postrust_sql::escape_ident)
1100                    .collect::<Vec<_>>()
1101                    .join(".");
1102                sqlx::query(&format!("SELECT {qualified}()"))
1103                    .execute(&mut *tx)
1104                    .await
1105                    .map_err(map_sqlx_error)?;
1106            }
1107
1108            // Execute main query with bound parameters
1109            let rows = bind_params(sqlx::query(&sql), &params)
1110                .fetch_all(&mut *tx)
1111                .await
1112                .map_err(|e| {
1113                    error!("Query error: {}", e);
1114                    map_sqlx_error(e)
1115                })?;
1116
1117            // Convert rows to JSON.
1118            //
1119            // `into_iter` matters for large result sets: each row's buffers are
1120            // freed as soon as it has been converted, rather than the whole
1121            // `Vec<PgRow>` staying alive alongside the whole `Vec<Value>`.
1122            // A rendered media type is a single value, not a row set: the
1123            // aggregate already produced the entire body.
1124            if let Some((media_type, base_type)) = media_handler
1125                .as_ref()
1126                .map(|(m, _, _, base)| (m.clone(), base.clone()))
1127                .or_else(|| declared_media_type(db_plan, api_request))
1128            {
1129                // The value's type is a domain this side has no mapping for,
1130                // so it arrives as PostgreSQL's text rendering of it. For
1131                // anything text-like that rendering *is* the value; for a
1132                // `bytea` it is `\x` and hex, which describes the bytes
1133                // rather than being them.
1134                let mut body = rows
1135                    .first()
1136                    .and_then(|row| raw_column(row, &base_type))
1137                    .unwrap_or_default();
1138
1139                // PostgREST reads a handler's value as raw bytes in binary
1140                // format, and a `jsonb` in binary format begins with a version
1141                // byte -- which it forwards to the client. Its own suite
1142                // records the result and marks it: "TODO SOH (start of
1143                // heading) is being added to results".
1144                //
1145                // It is a bug, and the byte makes the body invalid JSON, so it
1146                // is not something to do by default. Compatibility mode is
1147                // where this server sets out to be PostgREST rather than to be
1148                // right, and a client written against PostgREST's bytes gets
1149                // PostgREST's bytes.
1150                if state.config.compat_mode && base_type == "jsonb" {
1151                    body.insert(0, 0x01);
1152                }
1153
1154                // A rendered body is still a window on a result set, and
1155                // PostgREST reports one. The row count comes from the
1156                // aggregate's own statement where a handler rendered the rows;
1157                // where the value is a function's media-type domain there was
1158                // no aggregate, and the rows in hand are the answer.
1159                let rendered_rows = match media_handler.is_some() {
1160                    true => rows
1161                        .first()
1162                        .and_then(|row| {
1163                            sqlx::Row::try_get::<i64, _>(row, MEDIA_ROW_COUNT_COLUMN).ok()
1164                        })
1165                        .unwrap_or_default(),
1166                    false => rows.len() as i64,
1167                };
1168                let total = match (&api_request.preferences.count, &count_sql) {
1169                    (Some(preference), Some(count_sql)) => {
1170                        resolve_count(
1171                            &mut tx,
1172                            preference,
1173                            count_sql,
1174                            &params,
1175                            api_request.max_rows,
1176                        )
1177                        .await?
1178                    }
1179                    _ => None,
1180                };
1181                let content_range = postrust_response::ContentRange::from_pagination(
1182                    top_level_offset(api_request),
1183                    rendered_rows,
1184                    total,
1185                );
1186
1187                tx.commit()
1188                    .await
1189                    .map_err(|e| postrust_core::Error::ConnectionPool(e.to_string()))?;
1190                return Ok(QueryResult {
1191                    status: StatusCode::OK,
1192                    raw_body: Some((media_type, body)),
1193                    content_range: Some(content_range),
1194                    ..QueryResult::default()
1195                });
1196            }
1197
1198            let mut json_rows: Vec<serde_json::Value> = rows
1199                .into_iter()
1200                .map(|row| postrust_core::row_json::row_to_json(&row))
1201                .collect();
1202
1203            // Embeds already came back with the parent query when the SELECT
1204            // list carried relations. This path remains for anything the
1205            // single-query form did not handle.
1206            let two_query_parent = {
1207                let schema_cache = state.schema_cache().await;
1208                read_target(api_request, Some(db_plan), &schema_cache)
1209            };
1210            if let Some(parent_qi) = two_query_parent.filter(|_| !embed_level.saw_relations) {
1211                let schema_cache = state.schema_cache().await;
1212                embed_relations(
1213                    &mut tx,
1214                    &schema_cache,
1215                    &parent_qi,
1216                    &api_request.query_params.select,
1217                    &mut json_rows,
1218                    api_request.max_rows,
1219                )
1220                .await?;
1221            }
1222
1223            // A function can set the response's status and headers, and only
1224            // it knows whether it did -- so the settings are read back after
1225            // it has run, on the same transaction. Reads cannot set them
1226            // without a function of their own having run, which is what a
1227            // call or a mutation is.
1228            let (guc_headers, guc_status) =
1229                match is_mutation(db_plan) || matches!(db_plan, DbActionPlan::Call { .. }) {
1230                    false => (None, None),
1231                    true => {
1232                        use sqlx::Row;
1233                        let row = sqlx::query(
1234                            "SELECT current_setting('response.headers', true), \
1235                         current_setting('response.status', true)",
1236                        )
1237                        .fetch_one(&mut *tx)
1238                        .await
1239                        .map_err(map_sqlx_error)?;
1240                        // Empty is absent. Once any transaction on this
1241                        // connection has defined a custom setting, PostgreSQL
1242                        // keeps the name for the rest of the session and
1243                        // reports it as `''` rather than null -- so a request
1244                        // that set nothing would otherwise inherit "the empty
1245                        // headers" from whichever request last set some.
1246                        let setting = |index: usize| {
1247                            row.try_get::<Option<String>, _>(index)
1248                                .ok()
1249                                .flatten()
1250                                .filter(|value| !value.trim().is_empty())
1251                        };
1252                        (setting(0), setting(1))
1253                    }
1254                };
1255
1256            // Both are the function's own words, so both are checked before
1257            // they reach the response: a malformed one is a fault in the
1258            // schema, and saying so is more use than a header the client
1259            // cannot see or a status it cannot explain.
1260            if let Some(headers) = &guc_headers {
1261                if postrust_response::parse_guc_headers(headers).is_none() {
1262                    return Err(postrust_core::Error::InvalidGucHeaders);
1263                }
1264            }
1265            let guc_status = match guc_status {
1266                None => None,
1267                Some(status) => Some(
1268                    status
1269                        .trim()
1270                        .parse::<u16>()
1271                        .ok()
1272                        .and_then(|code| StatusCode::from_u16(code).ok())
1273                        .ok_or(postrust_core::Error::InvalidGucStatus)?,
1274                ),
1275            };
1276
1277            // The count runs on the same transaction and so under the same
1278            // snapshot as the page it describes.
1279            let total = match (&api_request.preferences.count, &count_sql) {
1280                (Some(preference), Some(count_sql)) => {
1281                    resolve_count(
1282                        &mut tx,
1283                        preference,
1284                        count_sql,
1285                        &params,
1286                        api_request.max_rows,
1287                    )
1288                    .await?
1289                }
1290                _ => None,
1291            };
1292
1293            // Reads take no locks worth holding and write plans commit their
1294            // work, so the transaction is committed either way.
1295            tx.commit()
1296                .await
1297                .map_err(|e| postrust_core::Error::ConnectionPool(e.to_string()))?;
1298
1299            // Drop columns that were only selected to join the embeds.
1300            //
1301            // Except where the embed answers to that very name: embedding
1302            // through a column -- `?select=client_id(*)` -- keys the result by
1303            // the column it joined on, and the embed is what the client asked
1304            // for. Stripping by name would take it along with the column.
1305            let embed_keys: std::collections::HashSet<&str> = embed_level
1306                .expressions
1307                .iter()
1308                .map(|(key, _)| key.as_str())
1309                .collect();
1310
1311            for column in added_join_columns
1312                .iter()
1313                .filter(|column| !embed_keys.contains(column.as_str()))
1314            {
1315                for row in json_rows.iter_mut() {
1316                    if let Some(object) = row.as_object_mut() {
1317                        object.remove(column);
1318                    }
1319                }
1320            }
1321
1322            // A spread's columns belong to the object above it. This happens
1323            // after the join columns are dropped, not before: a spread may
1324            // legitimately produce a column of the same name as one added for
1325            // joining, and dropping it afterwards would take the wrong one.
1326            let spread_columns: std::collections::HashMap<String, Vec<String>> =
1327                embed_level.spread_columns.iter().cloned().collect();
1328            for row in json_rows.iter_mut() {
1329                flatten_spreads(row, &spread_columns);
1330            }
1331
1332            // In PostgREST-compatibility mode, reshape RPC responses to match
1333            // PostgREST: un-nest the function-name-keyed column and return a
1334            // bare value for non-set-returning functions.
1335            let (mut json_rows, singular) = if state.config.compat_mode {
1336                if let ActionPlan::Db(DbActionPlan::Call { call, .. }) = plan {
1337                    unwrap_rpc_rows(json_rows, call)
1338                } else {
1339                    (json_rows, false)
1340                }
1341            } else {
1342                (json_rows, false)
1343            };
1344
1345            // A function returning `void` has no result to report. PostgREST
1346            // answers 204 rather than a body of `null`, which is the honest
1347            // shape for something that returns nothing.
1348            let returns_void = matches!(
1349                db_plan,
1350                DbActionPlan::Call { call, .. } if call.returns_void
1351            );
1352
1353            // `Prefer: max-affected` guards against a filter that turned out
1354            // to match more than the client meant. Nothing has been committed
1355            // yet, so refusing here undoes the whole statement.
1356            if let (Some(limit), postrust_core::api_request::PreferHandling::Strict) = (
1357                api_request.preferences.max_affected,
1358                &api_request.preferences.handling,
1359            ) {
1360                let affected = json_rows.len() as i64;
1361                let writes = is_mutation(db_plan) || matches!(db_plan, DbActionPlan::Call { .. });
1362                if writes && affected > limit {
1363                    return Err(postrust_core::Error::MaxAffectedExceeded(affected));
1364                }
1365            }
1366
1367            // Any row this statement created rather than merged into. Read
1368            // before the column is taken back out of the response.
1369            let created_a_row = reports_inserted
1370                && json_rows.iter().any(|row| {
1371                    row.get(postrust_core::query::INSERTED_COLUMN)
1372                        == Some(&serde_json::Value::Bool(true))
1373                });
1374            if reports_inserted {
1375                for row in json_rows.iter_mut() {
1376                    if let Some(object) = row.as_object_mut() {
1377                        object.remove(postrust_core::query::INSERTED_COLUMN);
1378                    }
1379                }
1380            }
1381
1382            // A `PUT` writes the row its URL names, and exactly that row. The
1383            // filters were applied to the body, so a body naming some other
1384            // row wrote nothing -- and one naming several wrote several. Both
1385            // are the same mistake, and PostgreSQL cannot report it because
1386            // neither is a database error.
1387            if is_upsert(api_request) && json_rows.len() != 1 {
1388                return Err(postrust_core::Error::PutMatchingPk);
1389            }
1390
1391            // The created row's own address, taken from its key and then
1392            // removed from the body -- the client asked for a `?select=`, not
1393            // for the key.
1394            let location = build_location(api_request, &mut json_rows, &location_keys);
1395
1396            // A mutation returns the affected rows only when the caller asked
1397            // for them; otherwise the body is empty whatever the status.
1398            let omit_body = is_mutation(db_plan) && !wants_representation(api_request);
1399            // Taken before the rows are dropped: how many a write touched is
1400            // still the answer to `Content-Range` and to a request for a
1401            // single object.
1402            let affected = json_rows.len();
1403            let rows = if omit_body { Vec::new() } else { json_rows };
1404
1405            // `application/geo+json` is a rendering of these same rows, done
1406            // here rather than in SQL so that everything a mutation reports
1407            // has already happened to them: the status derived, the location
1408            // taken from the key, the bookkeeping columns removed. Built in
1409            // SQL it saw the row as the statement left it, and leaked
1410            // `pgrst_inserted` into a feature's properties.
1411            let geojson_body = geojson_column
1412                .as_ref()
1413                .filter(|_| !omit_body)
1414                .map(|column| {
1415                    (
1416                        "application/geo+json".to_string(),
1417                        feature_collection(&rows, column),
1418                    )
1419                });
1420
1421            // PostgREST reports the returned window on every successful data
1422            // response -- reads, mutations and RPC alike, but not on errors or
1423            // OPTIONS. Without an exact count the total is unknown, which
1424            // renders as `*` and keeps the status at 200.
1425            // The window starts where the request asked it to, and `offset`
1426            // overrides the `Range` header exactly as it does when the plan
1427            // resolves the range -- otherwise the reported window says 0 for a
1428            // request that plainly skipped rows.
1429            let offset = top_level_offset(api_request);
1430
1431            // What a mutation reports is not a window on a result set. An
1432            // insert or a delete reports no window at all -- `*` -- because
1433            // the rows it wrote are not a page of anything; an update reports
1434            // how many it changed; and an upsert reports nothing, the client
1435            // having named the row itself. PostgREST draws exactly these
1436            // lines, and a client reading the header expects them.
1437            use postrust_core::api_request::Mutation;
1438            let content_range = match mutation_kind(api_request) {
1439                Some(Mutation::SingleUpsert) => None,
1440                Some(Mutation::Create) | Some(Mutation::Delete) => {
1441                    Some(postrust_response::ContentRange::new(1, 0, total))
1442                }
1443                Some(Mutation::Update) => Some(postrust_response::ContentRange::new(
1444                    0,
1445                    affected as i64 - 1,
1446                    total,
1447                )),
1448                None => Some(postrust_response::ContentRange::from_pagination(
1449                    offset,
1450                    rows.len() as i64,
1451                    total,
1452                )),
1453            };
1454
1455            // An offset past the end of the result is a range the server
1456            // cannot satisfy, and saying how many rows there actually are is
1457            // what lets the client correct it. Only reachable with a count
1458            // preference: without one the total is unknown and there is
1459            // nothing to be past the end of.
1460            if content_range
1461                .as_ref()
1462                .is_some_and(|range| range.status() == StatusCode::RANGE_NOT_SATISFIABLE)
1463                && mutation_kind(api_request).is_none()
1464            {
1465                return Err(postrust_core::Error::InvalidRange(format!(
1466                    "An offset of {} was requested, but there are only {} rows.",
1467                    offset,
1468                    total.unwrap_or(0)
1469                )));
1470            }
1471
1472            Ok(QueryResult {
1473                // A status the function set outright wins: it knows things
1474                // about the outcome that the request's shape cannot say.
1475                status: match (guc_status, returns_void) {
1476                    (Some(status), _) => status,
1477                    (None, true) => StatusCode::NO_CONTENT,
1478                    (None, false) => {
1479                        mutation_status(db_plan, api_request, created_a_row, reports_inserted)
1480                            .unwrap_or_else(|| {
1481                                content_range
1482                                    .as_ref()
1483                                    .map(postrust_response::ContentRange::status)
1484                                    .unwrap_or(StatusCode::OK)
1485                            })
1486                    }
1487                },
1488                rows,
1489                singular,
1490                omit_body: omit_body || returns_void,
1491                affected,
1492                location,
1493                content_range,
1494                guc_headers,
1495                raw_body: geojson_body,
1496                ..Default::default()
1497            })
1498        }
1499        ActionPlan::Info(info_plan) => {
1500            use postrust_core::plan::InfoPlan;
1501
1502            // Return appropriate metadata based on the info type
1503            let response_data = match info_plan {
1504                InfoPlan::OpenApiSpec => {
1505                    // Return basic server info for root endpoint
1506                    serde_json::json!({
1507                        "name": "postrust",
1508                        "version": env!("CARGO_PKG_VERSION"),
1509                        "description": "PostgREST-compatible REST API for PostgreSQL"
1510                    })
1511                }
1512                InfoPlan::RelationInfo(qi) => {
1513                    serde_json::json!({
1514                        "schema": qi.schema,
1515                        "name": qi.name,
1516                        "type": "relation"
1517                    })
1518                }
1519                InfoPlan::RoutineInfo(qi) => {
1520                    serde_json::json!({
1521                        "schema": qi.schema,
1522                        "name": qi.name,
1523                        "type": "routine"
1524                    })
1525                }
1526            };
1527
1528            Ok(QueryResult {
1529                status: StatusCode::OK,
1530                rows: vec![response_data],
1531                ..Default::default()
1532            })
1533        }
1534    }
1535}
1536
1537/// Answer an `OPTIONS` with the methods its resource actually has.
1538///
1539/// This runs outside the CORS layer because that layer answers every `OPTIONS`
1540/// itself and never calls what it wraps -- `if parts.method == Method::OPTIONS`
1541/// returns immediately, preflight or not. So an `OPTIONS` never reached the
1542/// request handler at all: the body was empty because nothing built one, and
1543/// `Allow` was missing because the code that knows the answer was never asked.
1544///
1545/// The CORS layer still writes the response, and this adds to it, so a
1546/// preflight keeps every header it had.
1547pub async fn options_allow(
1548    State(state): State<Arc<AppState>>,
1549    request: Request,
1550    next: axum::middleware::Next,
1551) -> Response {
1552    if request.method() != http::Method::OPTIONS {
1553        return next.run(request).await;
1554    }
1555
1556    // Not every route is the API's. A layer added to the router wraps every
1557    // one of them, so `/admin` and `/_` arrive here too, and answering those
1558    // with what a table allows -- or with "no such table" -- takes a working
1559    // preflight and turns it into a 404.
1560    let Some(path) = api_path(request.uri().path(), state.config.compat_mode) else {
1561        return next.run(request).await;
1562    };
1563
1564    let verbatim_db_errors = state.config.compat_mode;
1565
1566    // Built here, from borrows, before anything is awaited: `axum::body::Body`
1567    // is `Send` but not `Sync`, and `&T` is `Send` only where `T` is `Sync`, so
1568    // a `&Request` held across an await makes the whole future not `Send` --
1569    // which a middleware has to be.
1570    let probe = probe_request(&request, path);
1571    let allow = match probe {
1572        Ok(probe) => resolve_allow(&state, probe).await,
1573        Err(error) => Err(error),
1574    };
1575
1576    let mut response = next.run(request).await;
1577
1578    match allow {
1579        Ok(allow) => {
1580            if let Ok(value) = http::HeaderValue::from_str(&allow) {
1581                response.headers_mut().insert(http::header::ALLOW, value);
1582            }
1583            response
1584        }
1585        // Asking what may be done with a table that is not there is answered
1586        // the same way as asking for the table.
1587        Err(error) => {
1588            let mut refusal = error_response(error, verbatim_db_errors).into_response();
1589            // The CORS layer wrote its headers on the response this replaces.
1590            // Without them a browser reports the request as blocked by CORS
1591            // rather than as the 404 it earned, which is a different thing to
1592            // go and fix.
1593            for (name, value) in response.headers() {
1594                if name.as_str().starts_with("access-control-") || name == http::header::VARY {
1595                    refusal.headers_mut().append(name, value.clone());
1596                }
1597            }
1598            refusal
1599        }
1600    }
1601}
1602
1603/// The path the API parser should read, or `None` where the request names
1604/// something that is not the REST surface.
1605///
1606/// A layer added with `Router::layer` wraps each endpoint, and `nest` puts its
1607/// prefix-stripping *inside* that endpoint -- so the path seen here is the one
1608/// the client wrote and still carries the mount it arrived under, unlike the
1609/// one the handler reads.
1610fn api_path(path: &str, compat_mode: bool) -> Option<&str> {
1611    /// Whether a path names a mount point or something under it.
1612    fn under(path: &str, mount: &str) -> bool {
1613        path == mount || path.strip_prefix(mount).is_some_and(|r| r.starts_with('/'))
1614    }
1615
1616    // Every mount this server answers itself. In compatibility mode the REST
1617    // surface sits at the root, so a path not named here is read as a table:
1618    // `/healthz` becomes a question about a table called `healthz`, and the
1619    // two-segment ones are not a valid resource at all. Either way the
1620    // refusal replaces the preflight the CORS layer wrote, which is how a
1621    // browser comes to report a working endpoint as blocked.
1622    for mount in [
1623        "/admin",
1624        "/_",
1625        "/api/graphql",
1626        "/healthz",
1627        "/v1/version",
1628        "/v1/graphql",
1629        "/v1alpha1/graphql",
1630    ] {
1631        if under(path, mount) {
1632            return None;
1633        }
1634    }
1635    if under(path, "/api") {
1636        return Some(match path.strip_prefix("/api") {
1637            None | Some("") => "/",
1638            Some(rest) => rest,
1639        });
1640    }
1641    // Everywhere else the REST surface exists only in compatibility mode,
1642    // where it answers at the root. Without it `/` is this server's own
1643    // directory of endpoints, which allows what its handler allows.
1644    compat_mode.then_some(path)
1645}
1646
1647/// The request again, with an empty body and the API's own path, for the
1648/// parser to read.
1649fn probe_request(
1650    request: &Request,
1651    path: &str,
1652) -> Result<http::Request<bytes::Bytes>, postrust_core::Error> {
1653    let target = match request.uri().query() {
1654        Some(query) => format!("{path}?{query}"),
1655        None => path.to_string(),
1656    };
1657    let mut builder = http::Request::builder()
1658        .method(request.method().clone())
1659        .uri(target);
1660    for (name, value) in request.headers() {
1661        builder = builder.header(name, value);
1662    }
1663    builder
1664        .body(bytes::Bytes::new())
1665        .map_err(|e| postrust_core::Error::Internal(e.to_string()))
1666}
1667
1668/// What the resource this request names allows, or why it cannot say.
1669///
1670/// The path is resolved by the same parser the handler uses rather than by
1671/// splitting on `/` here, so a percent-encoded table name, an `Accept-Profile`
1672/// naming another schema, and the `/rpc/` prefix all mean what they mean
1673/// everywhere else.
1674async fn resolve_allow(
1675    state: &AppState,
1676    probe: http::Request<bytes::Bytes>,
1677) -> Result<String, postrust_core::Error> {
1678    use postrust_core::api_request::Action;
1679
1680    let api_request = parse_request(
1681        &probe,
1682        state.default_schema(),
1683        state.schemas(),
1684        state.config.db_max_rows,
1685    )?;
1686
1687    let schema_cache = state.schema_cache().await;
1688    match &api_request.action {
1689        Action::RelationInfo(qi) => Ok(relation_allow(schema_cache.require_table(qi)?)),
1690        Action::RoutineInfo { qi, .. } => Ok(routine_allow(schema_cache.get_routines(qi))),
1691        // The schema description, which is read and nothing else.
1692        _ => Ok("OPTIONS,GET,HEAD".to_string()),
1693    }
1694}
1695
1696/// The methods a relation answers, for the `Allow` of an OPTIONS.
1697///
1698/// Reading is always offered. Writing follows what the relation can actually
1699/// do, which for a view is what its triggers give it: a view with only an
1700/// INSERT trigger takes a POST and nothing else.
1701///
1702/// PUT is the exception that is not simply a permission. It replaces one row
1703/// named by its primary key, so a relation without a primary key cannot
1704/// answer it however writable it otherwise is -- which is why an auto-updatable
1705/// view without a key offers POST, PATCH and DELETE but not PUT.
1706///
1707fn relation_allow(table: &postrust_core::schema_cache::Table) -> String {
1708    let mut methods = vec!["OPTIONS", "GET", "HEAD"];
1709
1710    if table.insertable {
1711        methods.push("POST");
1712    }
1713    if table.insertable && table.updatable && !table.pk_cols.is_empty() {
1714        methods.push("PUT");
1715    }
1716    if table.updatable {
1717        methods.push("PATCH");
1718    }
1719    if table.deletable {
1720        methods.push("DELETE");
1721    }
1722
1723    methods.join(",")
1724}
1725
1726/// The methods a function answers, for the `Allow` of an OPTIONS.
1727///
1728/// A volatile function may change data, so it is reachable only by POST. One
1729/// that cannot can also be read, and is offered under GET and HEAD as well.
1730/// An overloaded name is readable when any of its overloads is.
1731fn routine_allow(routines: Option<&Vec<postrust_core::schema_cache::Routine>>) -> String {
1732    let readable = routines.is_some_and(|routines| {
1733        routines.iter().any(|routine| {
1734            !matches!(
1735                routine.volatility,
1736                postrust_core::schema_cache::FuncVolatility::Volatile
1737            )
1738        })
1739    });
1740
1741    match readable {
1742        true => "OPTIONS,GET,HEAD,POST".to_string(),
1743        false => "OPTIONS,POST".to_string(),
1744    }
1745}
1746
1747/// Reshape RPC rows for PostgREST-compatibility mode.
1748///
1749/// `SELECT * FROM func(...)` names its single output column after the function
1750/// (for scalar/`json` returns), which serializes to rows like
1751/// `[{"func": <value>}]`. PostgREST instead returns the bare value. This
1752/// un-nests that wrapper column and reports whether the result should be
1753/// rendered as a single (un-arrayed) value — i.e. when the function is not
1754/// set-returning.
1755///
1756/// The decision is driven by the plan's return-type metadata: composite and
1757/// `record` returns (`RETURNS TABLE`, row types) have real output columns and
1758/// are never un-nested, even when a single column happens to share the
1759/// function's name (e.g. `CREATE FUNCTION foo() RETURNS TABLE(foo int)`).
1760fn unwrap_rpc_rows(
1761    rows: Vec<serde_json::Value>,
1762    call: &CallPlan,
1763) -> (Vec<serde_json::Value>, bool) {
1764    let singular = !call.returns_set;
1765
1766    if call.returns_composite {
1767        return (rows, singular);
1768    }
1769
1770    let fname = call.function.name.as_str();
1771    let unwrapped = rows
1772        .into_iter()
1773        .map(|row| match row {
1774            serde_json::Value::Object(ref map) if map.len() == 1 && map.contains_key(fname) => {
1775                map.get(fname).cloned().unwrap_or(serde_json::Value::Null)
1776            }
1777            other => other,
1778        })
1779        .collect();
1780
1781    (unwrapped, singular)
1782}
1783
1784/// The table a read targets, if the request is a read.
1785fn read_target(
1786    api_request: &postrust_core::api_request::ApiRequest,
1787    db_plan: Option<&DbActionPlan>,
1788    schema_cache: &postrust_core::SchemaCache,
1789) -> Option<postrust_core::api_request::QualifiedIdentifier> {
1790    use postrust_core::api_request::{Action, DbAction};
1791
1792    match &api_request.action {
1793        Action::Db(DbAction::RelationRead { qi, .. }) => Some(qi.clone()),
1794        // The rows a mutation affected are rows of that table, so `?select=`
1795        // reaches through them to related resources exactly as it does on a
1796        // read -- `DELETE /tasks?select=name,project:projects(id)` says what
1797        // was deleted and what it belonged to.
1798        Action::Db(DbAction::RelationMut { qi, .. }) => Some(qi.clone()),
1799        // A function returning a table's rows embeds, and renders, exactly as
1800        // that table does.
1801        //
1802        // Which table that is was settled when the overload was chosen: a
1803        // name may carry several signatures returning different things, and
1804        // asking the cache again by name answers for whichever comes first.
1805        // It also knows the answer for a return type this side cannot read
1806        // off the name at all, such as a domain over a table.
1807        Action::Db(DbAction::Routine { qi, .. }) => match db_plan {
1808            Some(DbActionPlan::Call { read, .. }) => {
1809                read.as_ref().map(|tree| tree.root.from.clone())
1810            }
1811            _ => schema_cache
1812                .routine_returned_table(qi)
1813                .map(|table| table.qualified_identifier()),
1814        },
1815        _ => None,
1816    }
1817}
1818
1819/// One `?or=` or `?and=` over embedded resources, as a `WHERE` fragment.
1820///
1821/// Every leaf asks whether an embed matched, which is an existence test, and
1822/// the tree is what combines them. `None` where any leaf asks something else,
1823/// since half a condition is worse than none.
1824fn embed_logic_predicate(
1825    tree: &postrust_core::api_request::LogicTree,
1826    filterable: &[(String, String)],
1827) -> Option<String> {
1828    use postrust_core::api_request::{IsValue, LogicOperator, LogicTree, Operation};
1829
1830    match tree {
1831        LogicTree::Expr {
1832            negated,
1833            op,
1834            children,
1835        } => {
1836            let rendered = children
1837                .iter()
1838                .map(|child| embed_logic_predicate(child, filterable))
1839                .collect::<Option<Vec<String>>>()?;
1840            let joined = rendered.join(match op {
1841                LogicOperator::And => " AND ",
1842                LogicOperator::Or => " OR ",
1843            });
1844            Some(match negated {
1845                true => format!("NOT ({joined})"),
1846                false => format!("({joined})"),
1847            })
1848        }
1849        LogicTree::Stmt(filter) => {
1850            let (_, exists) = filterable
1851                .iter()
1852                .find(|(name, _)| name == &filter.field.name)?;
1853            // `is.null` asks for parents the embed did not match, so the
1854            // existence test is the negation of it.
1855            let negated = match &filter.op_expr.operation {
1856                Operation::Is(IsValue::Null) => filter.op_expr.negated,
1857                Operation::Is(IsValue::NotNull) => !filter.op_expr.negated,
1858                _ => return None,
1859            };
1860            Some(match negated {
1861                true => exists.clone(),
1862                false => format!("NOT {exists}"),
1863            })
1864        }
1865    }
1866}
1867
1868/// Round a geometry's numbers to nine decimal places.
1869///
1870/// Not cosmetic: nine is what `ST_AsGeoJSON` emits by default, and a geometry
1871/// read through the type's own rendering carries every digit the double has.
1872/// Nine decimal places of latitude is under a tenth of a millimetre, so the
1873/// digits past it describe floating-point representation rather than any
1874/// place on the earth.
1875fn round_coordinates(value: &mut serde_json::Value) {
1876    match value {
1877        serde_json::Value::Array(items) => items.iter_mut().for_each(round_coordinates),
1878        serde_json::Value::Object(fields) => fields.values_mut().for_each(round_coordinates),
1879        serde_json::Value::Number(number) => {
1880            let Some(float) = number.as_f64() else { return };
1881            if float.fract() == 0.0 {
1882                return;
1883            }
1884            // Through the decimal text rather than by arithmetic: scaling by a
1885            // power of ten and rounding back reintroduces exactly the digits
1886            // this is meant to drop.
1887            if let Ok(rounded) = format!("{float:.9}").parse::<f64>() {
1888                if let Some(rounded) = serde_json::Number::from_f64(rounded) {
1889                    *number = rounded;
1890                }
1891            }
1892        }
1893        _ => {}
1894    }
1895}
1896
1897/// Where the response's window starts.
1898///
1899/// The same resolution the plan itself used, asked again rather than restated:
1900/// the JSON path and the rendered-media path both report a `Content-Range`,
1901/// and a `Content-Range` that disagrees with the rows underneath it is worse
1902/// than none at all.
1903fn top_level_offset(api_request: &ApiRequest) -> i64 {
1904    postrust_core::plan::resolve_top_level_range(api_request).offset
1905}
1906
1907/// The first column of a row, as the bytes the client should receive.
1908///
1909/// A media type the schema declared is carried by a domain, and a domain is a
1910/// type this side has no decoder for -- so the value arrives as PostgreSQL's
1911/// own text rendering. For text, xml or json that rendering is the value. For
1912/// `bytea` it is `\x` followed by hex, which is a description of the bytes
1913/// and not the bytes, so it is decoded back.
1914fn raw_column(row: &sqlx::postgres::PgRow, base_type: &str) -> Option<Vec<u8>> {
1915    use sqlx::{Row, ValueRef};
1916
1917    let value = row.try_get_raw(0).ok()?;
1918    if value.is_null() {
1919        return None;
1920    }
1921    let bytes = value.as_bytes().ok()?.to_vec();
1922    if base_type != "bytea" {
1923        return Some(bytes);
1924    }
1925    // Text or binary, depending on whether this connection asked for a format
1926    // it could name -- so the `\x` rendering is decoded where it appears and
1927    // the bytes are passed through where they do not.
1928    let Some(hex) = bytes.strip_prefix(b"\\x") else {
1929        return Some(bytes);
1930    };
1931    hex.chunks(2)
1932        .map(|pair| {
1933            let digits = std::str::from_utf8(pair).ok()?;
1934            u8::from_str_radix(digits, 16).ok()
1935        })
1936        .collect::<Option<Vec<u8>>>()
1937        .or(Some(bytes))
1938}
1939
1940/// Whether the selection is the one a table has when none was asked for.
1941///
1942/// A handler declared for a table renders the table's own rows, so it applies
1943/// only where the request did not reshape them. `?select=*` is that shape
1944/// written out; `?select=id` and any embed are not.
1945fn has_default_select(select: &[postrust_core::api_request::SelectItem]) -> bool {
1946    use postrust_core::api_request::SelectItem;
1947
1948    match select {
1949        [] => true,
1950        [SelectItem::Field { field, alias, .. }] => field.name == "*" && alias.is_none(),
1951        _ => false,
1952    }
1953}
1954
1955/// Whether this server renders the type itself, with nothing declared for it.
1956fn renders_without_a_handler(media: &postrust_core::api_request::MediaType) -> bool {
1957    use postrust_core::api_request::MediaType;
1958
1959    matches!(
1960        media,
1961        MediaType::ApplicationJson
1962            | MediaType::Any
1963            | MediaType::TextCsv
1964            | MediaType::GeoJson
1965            | MediaType::SingularJson { .. }
1966            | MediaType::ArrayJson { .. }
1967            | MediaType::OpenApi
1968    )
1969}
1970
1971/// Render rows as a GeoJSON `FeatureCollection`.
1972///
1973/// The geometry arrives already rendered: a user-defined type is rendered by
1974/// the database, and what PostGIS renders a geometry as is GeoJSON -- with a
1975/// `crs` key, which a feature's geometry does not carry. Everything else on
1976/// the row is the feature's properties, the geometry excepted, since a
1977/// feature's geometry is not also one of its properties.
1978fn feature_collection(rows: &[serde_json::Value], column: &str) -> Vec<u8> {
1979    let features: Vec<serde_json::Value> = rows
1980        .iter()
1981        .map(|row| {
1982            let mut properties = row.clone();
1983            let geometry = properties
1984                .as_object_mut()
1985                .and_then(|fields| fields.remove(column))
1986                .map(|mut geometry| {
1987                    if let Some(fields) = geometry.as_object_mut() {
1988                        fields.remove("crs");
1989                    }
1990                    round_coordinates(&mut geometry);
1991                    geometry
1992                })
1993                .unwrap_or(serde_json::Value::Null);
1994            serde_json::json!({
1995                "type": "Feature",
1996                "geometry": geometry,
1997                "properties": properties,
1998            })
1999        })
2000        .collect();
2001    serde_json::to_vec(&serde_json::json!({
2002        "type": "FeatureCollection",
2003        "features": features,
2004    }))
2005    .unwrap_or_default()
2006}
2007
2008/// The key under which a GeoJSON feature's geometry is read.
2009///
2010/// A table may carry more than one geometry column, and `?select=` is what
2011/// says which of them the request is about: `select=id,name,range_area` asks
2012/// for a collection of areas, not for whichever geometry the catalogue
2013/// happens to list first. The name wanted is the one the response uses, so an
2014/// aliased column is found under its alias -- which is also the key that has
2015/// to come back out of `properties`, since a feature's geometry is not also
2016/// one of its properties.
2017///
2018/// `None` means the rows carry no geometry at all, which is not a GeoJSON
2019/// response with something missing from it but a request that cannot be
2020/// answered.
2021fn geometry_key(
2022    table: &postrust_core::schema_cache::Table,
2023    select: &[postrust_core::api_request::SelectItem],
2024) -> Option<String> {
2025    use postrust_core::api_request::SelectItem;
2026
2027    let is_geometry = |name: &str| {
2028        table
2029            .get_column(name)
2030            .is_some_and(|c| matches!(c.nominal_type.as_str(), "geometry" | "geography"))
2031    };
2032
2033    // `*` and a named column can both appear in one selection, and a column
2034    // named outright is the one meant even so: `select=*,range_area` is a
2035    // request about areas.
2036    let mut selects_every_column = select.is_empty();
2037    for item in select {
2038        if let SelectItem::Field { field, alias, .. } = item {
2039            if field.name == "*" {
2040                selects_every_column = true;
2041            } else if is_geometry(&field.name) {
2042                return Some(alias.clone().unwrap_or_else(|| field.name.clone()));
2043            }
2044        }
2045    }
2046
2047    if !selects_every_column {
2048        return None;
2049    }
2050    table
2051        .columns
2052        .values()
2053        .find(|c| matches!(c.nominal_type.as_str(), "geometry" | "geography"))
2054        .map(|c| c.name.clone())
2055}
2056
2057/// Whether any part of the selection, at any depth, asks for an aggregate.
2058fn selects_an_aggregate(api_request: &ApiRequest) -> bool {
2059    use postrust_core::api_request::SelectItem;
2060
2061    fn walk(items: &[SelectItem]) -> bool {
2062        items.iter().any(|item| match item {
2063            SelectItem::Field { aggregate, .. } => aggregate.is_some(),
2064            SelectItem::Relation { select, .. } => walk(select),
2065            SelectItem::SpreadRelation { select, .. } => walk(select),
2066        })
2067    }
2068
2069    walk(&api_request.query_params.select)
2070}
2071
2072/// Whether this request is a read, and so may run read-only.
2073///
2074/// It is the method that decides, not the plan: calling a function is a read
2075/// over GET and a write over POST, and the same function may be either. Asking
2076/// the plan instead would class every call as a read and refuse the writes a
2077/// POST is entitled to make.
2078fn is_read_only(api_request: &ApiRequest) -> bool {
2079    use postrust_core::api_request::{Action, DbAction, InvokeMethod};
2080
2081    match &api_request.action {
2082        Action::Db(DbAction::RelationRead { .. }) | Action::Db(DbAction::SchemaRead { .. }) => true,
2083        Action::Db(DbAction::Routine { invoke_method, .. }) => {
2084            matches!(invoke_method, InvokeMethod::InvRead { .. })
2085        }
2086        Action::Db(DbAction::RelationMut { .. }) => false,
2087        // Metadata only; nothing is executed against the database.
2088        Action::RelationInfo(_) | Action::RoutineInfo { .. } | Action::SchemaInfo => true,
2089    }
2090}
2091
2092/// The plan's SQL with its page removed, for counting what the filters match.
2093///
2094/// Only reads and calls are counted. A mutation's `Content-Range` reports the
2095/// rows it affected, which is the result set itself.
2096fn unpaged_sql(
2097    db_plan: &postrust_core::plan::DbActionPlan,
2098    role: &str,
2099) -> Result<Option<String>, postrust_core::Error> {
2100    use postrust_core::plan::{ActionPlan, DbActionPlan};
2101
2102    let unpaged = match db_plan {
2103        DbActionPlan::Read(tree) => {
2104            let mut tree = tree.clone();
2105            tree.root.range = Default::default();
2106            DbActionPlan::Read(tree)
2107        }
2108        DbActionPlan::Call { call, read } => {
2109            let read = read.as_ref().map(|tree| {
2110                let mut tree = tree.clone();
2111                tree.root.range = Default::default();
2112                tree
2113            });
2114            DbActionPlan::Call {
2115                call: call.clone(),
2116                read,
2117            }
2118        }
2119        DbActionPlan::MutateRead { .. } => return Ok(None),
2120    };
2121
2122    let query = postrust_core::query::build_query(&ActionPlan::Db(unpaged), Some(role))?;
2123    Ok(query.has_main().then(|| query.build_main().0))
2124}
2125
2126/// The total the `Prefer: count` asked for, or `None` when it asked for none.
2127///
2128/// `exact` counts the rows the filters match. `planned` asks the query planner
2129/// what it expects without running anything, which is cheap on a large table
2130/// and correspondingly rough. `estimated` is the planner's guess only where it
2131/// matters: below the server's row ceiling the exact count is already paid for,
2132/// so it is used, and above it the larger of the two is reported -- an estimate
2133/// that came in under a count we have actually taken would be plainly wrong.
2134async fn resolve_count(
2135    tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
2136    preference: &postrust_core::api_request::PreferCount,
2137    count_sql: &str,
2138    params: &[postrust_sql::SqlParam],
2139    max_rows: Option<i64>,
2140) -> Result<Option<i64>, postrust_core::Error> {
2141    use postrust_core::api_request::PreferCount;
2142
2143    match preference {
2144        PreferCount::Exact => exact_count(tx, count_sql, params).await,
2145        PreferCount::Planned => planned_count(tx, count_sql, params).await,
2146        PreferCount::Estimated => match (exact_count(tx, count_sql, params).await?, max_rows) {
2147            (Some(exact), Some(ceiling)) if exact <= ceiling => Ok(Some(exact)),
2148            (Some(exact), _) => {
2149                let planned = planned_count(tx, count_sql, params).await?;
2150                Ok(Some(exact.max(planned.unwrap_or(exact))))
2151            }
2152            (None, _) => planned_count(tx, count_sql, params).await,
2153        },
2154    }
2155}
2156
2157/// The number of rows the filters match.
2158async fn exact_count(
2159    tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
2160    count_sql: &str,
2161    params: &[postrust_sql::SqlParam],
2162) -> Result<Option<i64>, postrust_core::Error> {
2163    use sqlx::Row;
2164
2165    let sql = format!("SELECT count(*) FROM ({}) AS pgrst_count", count_sql);
2166    let row = bind_params(sqlx::query(&sql), params)
2167        .fetch_one(&mut **tx)
2168        .await
2169        .map_err(map_sqlx_error)?;
2170    Ok(row.try_get::<i64, _>(0).ok())
2171}
2172
2173/// The number of rows the query planner expects, without running the query.
2174async fn planned_count(
2175    tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
2176    count_sql: &str,
2177    params: &[postrust_sql::SqlParam],
2178) -> Result<Option<i64>, postrust_core::Error> {
2179    use sqlx::Row;
2180
2181    let sql = format!("EXPLAIN (FORMAT JSON) {}", count_sql);
2182    let row = bind_params(sqlx::query(&sql), params)
2183        .fetch_one(&mut **tx)
2184        .await
2185        .map_err(map_sqlx_error)?;
2186    let plan: serde_json::Value = row.try_get(0).unwrap_or(serde_json::Value::Null);
2187    Ok(plan
2188        .get(0)
2189        .and_then(|node| node.get("Plan"))
2190        .and_then(|node| node.get("Plan Rows"))
2191        .and_then(serde_json::Value::as_i64))
2192}
2193
2194/// Whether this plan changes data.
2195fn is_mutation(db_plan: &postrust_core::plan::DbActionPlan) -> bool {
2196    matches!(
2197        db_plan,
2198        postrust_core::plan::DbActionPlan::MutateRead { .. }
2199    )
2200}
2201
2202/// Whether the caller asked for the affected rows back.
2203fn wants_representation(api_request: &ApiRequest) -> bool {
2204    matches!(
2205        api_request.preferences.representation,
2206        postrust_core::api_request::PreferRepresentation::Full
2207    )
2208}
2209
2210/// Whether a created row's address is worth reporting.
2211///
2212/// `Prefer: return=headers-only` asks for exactly that and nothing else, and
2213/// is the only request PostgREST answers with a `Location`. A caller taking
2214/// the row back reads the key out of the body; a caller wanting neither asked
2215/// for a minimal response and gets one.
2216fn wants_location(api_request: &ApiRequest) -> bool {
2217    matches!(
2218        api_request.preferences.representation,
2219        postrust_core::api_request::PreferRepresentation::HeadersOnly
2220    )
2221}
2222
2223/// The status PostgREST gives a successful mutation, or `None` for anything
2224/// that isn't one.
2225///
2226/// An insert reports 201 Created. An update or delete reports 204 No Content
2227/// unless the caller asked for the rows back, in which case there is content
2228/// to report and it is a plain 200.
2229fn mutation_status(
2230    db_plan: &postrust_core::plan::DbActionPlan,
2231    api_request: &ApiRequest,
2232    created_a_row: bool,
2233    // Whether the statement was in a position to say. A write with no conflict
2234    // clause could only have created rows, and one on a relation that will not
2235    // give up `xmax` cannot tell either way -- in both cases there is nothing
2236    // to report but the creation.
2237    could_have_merged: bool,
2238) -> Option<StatusCode> {
2239    use postrust_core::api_request::PreferResolution;
2240    use postrust_core::plan::{DbActionPlan, MutatePlan};
2241
2242    let DbActionPlan::MutateRead { mutate, .. } = db_plan else {
2243        return None;
2244    };
2245
2246    let merging = matches!(
2247        api_request.preferences.resolution,
2248        Some(PreferResolution::MergeDuplicates)
2249    );
2250
2251    Some(match mutate {
2252        // A `PUT` names the row it is writing, so from the client's side
2253        // nothing was discovered by the response -- it reports the outcome
2254        // only where it asked to see the row.
2255        MutatePlan::Insert { .. } if is_upsert(api_request) => {
2256            match (
2257                wants_representation(api_request),
2258                created_a_row || !could_have_merged,
2259            ) {
2260                (false, _) => StatusCode::NO_CONTENT,
2261                (true, true) => StatusCode::CREATED,
2262                (true, false) => StatusCode::OK,
2263            }
2264        }
2265        // A `POST` that merged into every row it touched created nothing, and
2266        // 201 would be a promise that something new is at the `Location`.
2267        MutatePlan::Insert { .. } if merging && could_have_merged && !created_a_row => {
2268            StatusCode::OK
2269        }
2270        MutatePlan::Insert { .. } => StatusCode::CREATED,
2271        _ => match wants_representation(api_request) {
2272            true => StatusCode::OK,
2273            false => StatusCode::NO_CONTENT,
2274        },
2275    })
2276}
2277
2278/// Ensure every embedded relation's join column is selected on the parent.
2279///
2280/// Returns the columns that were added purely to make the join possible, so
2281/// they can be removed from the response afterwards. An empty select list means
2282/// "all columns", in which case nothing needs adding.
2283fn add_embed_join_columns(
2284    api_request: &mut postrust_core::api_request::ApiRequest,
2285    schema_cache: &postrust_core::SchemaCache,
2286) -> Result<Vec<String>, postrust_core::Error> {
2287    use postrust_core::api_request::{Field, SelectItem};
2288
2289    let Some(parent_qi) = read_target(api_request, None, schema_cache) else {
2290        return Ok(Vec::new());
2291    };
2292
2293    let select = &api_request.query_params.select;
2294    if select.is_empty() {
2295        return Ok(Vec::new());
2296    }
2297
2298    // `*` already names every column, so no join column needs adding -- and
2299    // adding one anyway selects it twice, which makes `src.*` ambiguous. The
2300    // relations are still walked: a computed relationship needs the parent's
2301    // row carried out, and `*` does not provide that.
2302    let selects_everything = select
2303        .iter()
2304        .any(|item| matches!(item, SelectItem::Field { field, .. } if field.name == "*"));
2305
2306    // Only a column selected under its own name serves as a join key. An
2307    // aliased one -- `myId:id` -- arrives in the result as `myId`, so the
2308    // correlation would look for a column the inner query no longer has; the
2309    // same goes for one that was cast or reached into with a JSON path.
2310    let selected: std::collections::HashSet<String> = select
2311        .iter()
2312        .filter_map(|item| match item {
2313            SelectItem::Field {
2314                field,
2315                aggregate: None,
2316                cast: None,
2317                alias: None,
2318                ..
2319            } if field.json_path.is_empty() => Some(field.name.clone()),
2320            _ => None,
2321        })
2322        .collect();
2323
2324    let mut added = Vec::new();
2325
2326    for item in select.clone() {
2327        // A spread joins on a parent column exactly as a plain embed does, so
2328        // it needs that column selected just the same. Without it the parent
2329        // row has no key to match children against and every spread column
2330        // comes back null.
2331        let (relation, hint) = match &item {
2332            SelectItem::Relation { relation, hint, .. } => (relation, hint),
2333            SelectItem::SpreadRelation { relation, hint, .. } => (relation, hint),
2334            SelectItem::Field { .. } => continue,
2335        };
2336
2337        let rel = schema_cache
2338            .find_relationship(&parent_qi, relation, hint.as_deref(), &parent_qi.schema)?
2339            .ok_or_else(|| {
2340                schema_cache.relationship_not_found(
2341                    &parent_qi,
2342                    relation,
2343                    hint.as_deref(),
2344                    &parent_qi.schema,
2345                )
2346            })?;
2347
2348        let plan = postrust_core::embed::EmbedPlan::resolve(rel, schema_cache)?;
2349
2350        // A computed relationship joins on nothing -- the parent row is the
2351        // function's argument. That row has to be carried out of the inner
2352        // query, which is a column of its own rather than a join key. After a
2353        // mutation it is carried out of the statement itself, by `RETURNING`,
2354        // because that is the last place the row still has the table's type.
2355        if plan.function.is_some() {
2356            if !added.iter().any(|c| c == PARENT_ROW_COLUMN) {
2357                added.push(PARENT_ROW_COLUMN.to_string());
2358            }
2359            continue;
2360        }
2361
2362        // Every column the join is on, not just the first: a foreign key over
2363        // several columns correlates on all of them, and one left unselected
2364        // is a column the correlation cannot see.
2365        let join_columns: Vec<String> = match plan.columns.is_empty() {
2366            true => vec![plan.local_column.clone()],
2367            false => plan
2368                .columns
2369                .iter()
2370                .map(|(local, _)| local.clone())
2371                .collect(),
2372        };
2373
2374        if selects_everything {
2375            continue;
2376        }
2377
2378        for column in join_columns {
2379            if !selected.contains(&column) && !added.contains(&column) {
2380                api_request.query_params.select.push(SelectItem::Field {
2381                    field: Field::simple(&column),
2382                    aggregate: None,
2383                    aggregate_cast: None,
2384                    cast: None,
2385                    alias: None,
2386                });
2387                added.push(column);
2388            }
2389        }
2390    }
2391
2392    Ok(added)
2393}
2394
2395/// Build the SELECT-list expressions that embed relations in one query.
2396///
2397/// Returns one `(field_name, expression)` per requested relation. Each
2398/// expression is a correlated subselect yielding JSON, so the whole tree comes
2399/// back from the parent query rather than from a query per relation per level.
2400///
2401/// `alias_counter` hands out a distinct alias per level, so a self-referential
2402/// relationship stays unambiguous.
2403/// Filters addressed at embedded resources, and the parameters they bind.
2404///
2405/// The embed expressions are assembled as SQL text and wrapped around a main
2406/// query that already owns `$1..$n`, so any placeholder introduced here has to
2407/// be renumbered past that point and its value appended in the same order.
2408struct EmbedFilters<'a> {
2409    /// Every path-scoped filter on the request, e.g. `(["clients"], id=eq.1)`.
2410    filters: &'a [(
2411        postrust_core::api_request::EmbedPath,
2412        postrust_core::api_request::Filter,
2413    )],
2414    /// Values bound by the predicates built so far, in placeholder order.
2415    params: Vec<postrust_sql::SqlParam>,
2416    /// How many parameters the main query already uses.
2417    base: usize,
2418    /// Ordering asked of each embedded resource, by path.
2419    orders: &'a [(
2420        postrust_core::api_request::EmbedPath,
2421        Vec<postrust_core::api_request::OrderTerm>,
2422    )],
2423    /// Ranges asked of each embedded resource, by dotted path.
2424    ranges: &'a std::collections::HashMap<String, postrust_core::api_request::Range>,
2425    /// `and=`/`or=` groups asked of each embedded resource, by path.
2426    logic: &'a [(
2427        postrust_core::api_request::EmbedPath,
2428        postrust_core::api_request::LogicTree,
2429    )],
2430    /// Row cap applied to each embedded resource.
2431    max_rows: Option<i64>,
2432    /// Source of unique subquery aliases across the whole embed tree.
2433    alias_counter: usize,
2434    /// How an embedded resource is rendered.
2435    ///
2436    /// `Verbatim` everywhere except under a media handler that stringifies the
2437    /// whole row: there each column is rendered as *text*, and a `json` column
2438    /// renders exactly as written where PostgREST's `jsonb` one renders
2439    /// normalised. See [`postrust_core::EmbedRendering`].
2440    rendering: postrust_core::EmbedRendering,
2441}
2442
2443/// Renumber the placeholders in `sql` so they start after `offset`.
2444///
2445/// Scans rather than string-replaces: a naive replacement of `$1` would also
2446/// corrupt the `$1` inside `$10`.
2447fn shift_placeholders(sql: &str, offset: usize) -> String {
2448    let mut out = String::with_capacity(sql.len());
2449    let mut chars = sql.char_indices().peekable();
2450
2451    while let Some((i, ch)) = chars.next() {
2452        if ch != '$' {
2453            out.push(ch);
2454            continue;
2455        }
2456        let start = i + 1;
2457        let mut end = start;
2458        while let Some((j, d)) = chars.peek() {
2459            if d.is_ascii_digit() {
2460                end = j + d.len_utf8();
2461                chars.next();
2462            } else {
2463                break;
2464            }
2465        }
2466        match sql[start..end].parse::<usize>() {
2467            Ok(n) => out.push_str(&format!("${}", n + offset)),
2468            Err(_) => out.push('$'),
2469        }
2470    }
2471
2472    out
2473}
2474
2475impl EmbedFilters<'_> {
2476    /// A fresh alias, unique across the embed tree.
2477    fn next_alias(&mut self) -> String {
2478        self.alias_counter += 1;
2479        format!("e{}", self.alias_counter)
2480    }
2481
2482    /// The `WHERE` fragment for one embedded resource, or `None` if unfiltered.
2483    /// The row window an embedded resource was asked for.
2484    ///
2485    /// The server's own cap still applies, so an embed cannot be asked for
2486    /// more rows than the server is willing to return.
2487    fn range_for(&self, path: &[String], names: &[String]) -> (Option<i64>, i64) {
2488        let range = self
2489            .ranges
2490            .get(&path.join("."))
2491            .or_else(|| self.ranges.get(&names.join(".")));
2492        let requested = range.and_then(|r| r.limit);
2493        let offset = range.map(|r| r.offset).unwrap_or(0);
2494        let limit = match (requested, self.max_rows) {
2495            (Some(limit), Some(cap)) => Some(limit.min(cap)),
2496            (Some(limit), None) => Some(limit),
2497            (None, cap) => cap,
2498        };
2499        (limit, offset)
2500    }
2501
2502    fn predicate_for(
2503        &mut self,
2504        path: &[String],
2505        names: &[String],
2506        child_qi: &postrust_core::api_request::QualifiedIdentifier,
2507        schema_cache: &postrust_core::SchemaCache,
2508    ) -> Result<Option<String>, postrust_core::Error> {
2509        let addresses = |candidate: &[String]| candidate == path || candidate == names;
2510
2511        let matching: Vec<_> = self
2512            .filters
2513            .iter()
2514            .filter(|(filter_path, _)| addresses(filter_path))
2515            .map(|(_, filter)| filter.clone())
2516            .collect();
2517
2518        let matching_logic: Vec<_> = self
2519            .logic
2520            .iter()
2521            .filter(|(logic_path, _)| addresses(logic_path))
2522            .map(|(_, tree)| tree.clone())
2523            .collect();
2524
2525        if matching.is_empty() && matching_logic.is_empty() {
2526            return Ok(None);
2527        }
2528
2529        let table = schema_cache.get_table(child_qi);
2530        let mut parts = Vec::with_capacity(matching.len());
2531
2532        for filter in &matching {
2533            // Inside the child's subselect an unqualified name binds to the
2534            // child, but only because the child has that column: an unknown
2535            // name would resolve outward to the correlated parent and filter
2536            // the wrong table silently. Refuse instead.
2537            let column = table
2538                .and_then(|t| t.columns.get(&filter.field.name))
2539                .ok_or_else(|| {
2540                    postrust_core::Error::ColumnNotFound(format!(
2541                        "{}.{}",
2542                        child_qi.name, filter.field.name
2543                    ))
2544                })?;
2545
2546            let frag =
2547                postrust_core::query::QueryBuilder::filter_sql(filter, &column.nominal_type)?;
2548            parts.push(shift_placeholders(
2549                frag.sql(),
2550                self.base + self.params.len(),
2551            ));
2552            self.params.extend(frag.params().iter().cloned());
2553        }
2554
2555        for tree in matching_logic {
2556            // A name the child does not have would bind outward to the
2557            // correlated parent and filter the wrong table, exactly as a plain
2558            // filter would, so the tree is checked before it is rendered.
2559            let mut unknown = None;
2560            check_logic_columns(&tree, table, &mut unknown);
2561            if let Some(name) = unknown {
2562                return Err(postrust_core::Error::ColumnNotFound(format!(
2563                    "{}.{}",
2564                    child_qi.name, name
2565                )));
2566            }
2567
2568            let resolver = |name: &str| -> String {
2569                table
2570                    .and_then(|t| t.get_column(name))
2571                    .map(|c| c.nominal_type.clone())
2572                    .unwrap_or_else(|| "text".to_string())
2573            };
2574            let frag = postrust_core::query::QueryBuilder::logic_sql(&tree, resolver)?;
2575            parts.push(shift_placeholders(
2576                frag.sql(),
2577                self.base + self.params.len(),
2578            ));
2579            self.params.extend(frag.params().iter().cloned());
2580        }
2581
2582        Ok(Some(format!("({})", parts.join(" AND "))))
2583    }
2584}
2585
2586/// Record the first column a logic tree names that the table does not have.
2587fn check_logic_columns(
2588    tree: &postrust_core::api_request::LogicTree,
2589    table: Option<&postrust_core::schema_cache::Table>,
2590    unknown: &mut Option<String>,
2591) {
2592    use postrust_core::api_request::LogicTree;
2593
2594    match tree {
2595        LogicTree::Expr { children, .. } => {
2596            for child in children {
2597                check_logic_columns(child, table, unknown);
2598            }
2599        }
2600        LogicTree::Stmt(filter) => {
2601            if unknown.is_none()
2602                && table
2603                    .map(|t| t.get_column(&filter.field.name).is_none())
2604                    .unwrap_or(true)
2605            {
2606                *unknown = Some(filter.field.name.clone());
2607            }
2608        }
2609    }
2610}
2611
2612/// One level of the embed tree.
2613#[derive(Default)]
2614struct EmbedLevel {
2615    /// `(response key, SQL expression)` for each relation at this level.
2616    expressions: Vec<(String, String)>,
2617    /// `EXISTS` predicates from `!inner` on these relations. They reference
2618    /// the alias of the level *above*, so the caller applies them.
2619    inner_joins: Vec<String>,
2620    /// `ORDER BY` expressions for terms naming an embedded resource.
2621    orders: Vec<String>,
2622    /// Each embed under the name the client used for it, with its
2623    /// expression. A spread's response key is internal, so this is what a
2624    /// filter naming the embed -- `?clients=is.null` -- is matched against.
2625    filterable: Vec<(String, String)>,
2626    /// For each spread, the keys it contributes to its parent.
2627    ///
2628    /// Needed because a spread that matched nothing still carries them --
2629    /// as nulls, or as empty arrays for a to-many -- and by then there is no
2630    /// row to read the names off.
2631    spread_columns: Vec<(String, Vec<String>)>,
2632    /// Whether this level had any relation to embed at all.
2633    ///
2634    /// Distinct from having produced an expression: `clients()` is a relation
2635    /// that deliberately contributes nothing, and the fallback path must not
2636    /// take an absent expression for a relation the single-query form could
2637    /// not handle.
2638    saw_relations: bool,
2639}
2640
2641/// The keys a spread's selection contributes to the object above it.
2642///
2643/// `*` names every column of the related table, an alias renames the column it
2644/// precedes, and a nested spread contributes its own keys, having already been
2645/// flattened into this one.
2646fn spread_output_names(
2647    schema_cache: &postrust_core::SchemaCache,
2648    child_qi: &postrust_core::api_request::QualifiedIdentifier,
2649    select: &[postrust_core::api_request::SelectItem],
2650) -> Vec<String> {
2651    use postrust_core::api_request::SelectItem;
2652
2653    let mut names = Vec::new();
2654    for item in select {
2655        match item {
2656            SelectItem::Field { field, .. } if field.name == "*" => {
2657                if let Some(table) = schema_cache.get_table(child_qi) {
2658                    names.extend(table.columns.keys().cloned());
2659                }
2660            }
2661            SelectItem::Field { field, alias, .. } => {
2662                names.push(alias.clone().unwrap_or_else(|| field.name.clone()));
2663            }
2664            SelectItem::Relation {
2665                relation, alias, ..
2666            } => names.push(alias.clone().unwrap_or_else(|| relation.clone())),
2667            SelectItem::SpreadRelation {
2668                relation, select, ..
2669            } => {
2670                if let Some(rel) = schema_cache
2671                    .find_relationship(child_qi, relation, None, &child_qi.schema)
2672                    .ok()
2673                    .flatten()
2674                {
2675                    let target = rel.foreign_table().clone();
2676                    names.extend(spread_output_names(schema_cache, &target, select));
2677                }
2678            }
2679        }
2680    }
2681    names
2682}
2683
2684/// Dissolve spread embeds into the objects that contain them.
2685///
2686/// A spread arrives as an ordinary JSON column under a reserved name. This
2687/// walks the response and, wherever it finds one, moves its columns up into
2688/// the surrounding object and drops the name.
2689///
2690/// It recurses first, so a spread nested inside another is already flattened
2691/// by the time its parent is -- which is what carries a grandchild's columns
2692/// all the way up.
2693///
2694/// Spreading a to-many gives each column an array of that column's values
2695/// across the matched rows, rather than an array of objects.
2696fn flatten_spreads(
2697    value: &mut serde_json::Value,
2698    columns: &std::collections::HashMap<String, Vec<String>>,
2699) {
2700    match value {
2701        serde_json::Value::Array(items) => {
2702            for item in items {
2703                flatten_spreads(item, columns);
2704            }
2705        }
2706        serde_json::Value::Object(object) => {
2707            for (_, child) in object.iter_mut() {
2708                flatten_spreads(child, columns);
2709            }
2710
2711            let spread_keys: Vec<String> = object
2712                .keys()
2713                .filter(|key| key.starts_with(SPREAD_KEY_PREFIX))
2714                .cloned()
2715                .collect();
2716
2717            for key in spread_keys {
2718                let Some(spread) = object.remove(&key) else {
2719                    continue;
2720                };
2721                let names = columns.get(&key).cloned().unwrap_or_default();
2722                match spread {
2723                    serde_json::Value::Object(columns) => {
2724                        for (name, column) in columns {
2725                            object.insert(name, column);
2726                        }
2727                    }
2728                    // A to-many: one array per column rather than an array of
2729                    // objects, so the rows are transposed. With no rows the
2730                    // columns are still named, each holding an empty array.
2731                    serde_json::Value::Array(rows) => {
2732                        for name in names {
2733                            let column = rows
2734                                .iter()
2735                                .map(|row| {
2736                                    row.get(&name).cloned().unwrap_or(serde_json::Value::Null)
2737                                })
2738                                .collect();
2739                            object.insert(name, serde_json::Value::Array(column));
2740                        }
2741                    }
2742                    // The relationship matched nothing. The keys are still
2743                    // the client's to expect, so they are there, holding null.
2744                    _ => {
2745                        for name in names {
2746                            object.insert(name, serde_json::Value::Null);
2747                        }
2748                    }
2749                }
2750            }
2751        }
2752        _ => {}
2753    }
2754}
2755
2756/// The relation an embed names, found by the name the response uses.
2757fn embedded_by_name<'a>(
2758    select: &'a [postrust_core::api_request::SelectItem],
2759    name: &str,
2760) -> Option<(&'a str, Option<&'a str>)> {
2761    use postrust_core::api_request::SelectItem;
2762
2763    select.iter().find_map(|item| match item {
2764        SelectItem::Relation {
2765            relation,
2766            alias,
2767            hint,
2768            ..
2769        } if alias.as_deref().unwrap_or(relation) == name => {
2770            Some((relation.as_str(), hint.as_deref()))
2771        }
2772        // A spread has no alias -- nothing is being named -- so it answers to
2773        // the relation's own name.
2774        SelectItem::SpreadRelation { relation, hint, .. } if relation == name => {
2775            Some((relation.as_str(), hint.as_deref()))
2776        }
2777        _ => None,
2778    })
2779}
2780
2781/// Build the `ORDER BY` expressions for terms naming an embedded resource.
2782///
2783/// `order=clients(name)` orders by a column the parent does not have, so each
2784/// term becomes a scalar subselect fetched per parent row -- the same shape as
2785/// the embed itself, reduced to one column.
2786fn build_embed_orders(
2787    schema_cache: &postrust_core::SchemaCache,
2788    parent_qi: &postrust_core::api_request::QualifiedIdentifier,
2789    order: &[postrust_core::plan::CoercibleOrderTerm],
2790    select: &[postrust_core::api_request::SelectItem],
2791    ctx: &mut EmbedFilters<'_>,
2792) -> Result<Vec<String>, postrust_core::Error> {
2793    let mut orders = Vec::new();
2794
2795    for term in order.iter() {
2796        let Some(relation) = &term.relation else {
2797            continue;
2798        };
2799
2800        // `order=` names a resource this request embedded, by the name the
2801        // response gives it -- so `pros:projects(*)` is ordered on as `pros`,
2802        // and the table it came from is what the relationship is looked up
2803        // by. A name the selection does not carry is not an embedded resource
2804        // here, whether or not the schema has a table by that name.
2805        let embedded = embedded_by_name(select, relation)
2806            .ok_or_else(|| postrust_core::Error::NotAnEmbeddedResource(relation.clone()))?;
2807
2808        let rel = schema_cache
2809            .find_relationship(parent_qi, embedded.0, embedded.1, &parent_qi.schema)?
2810            .ok_or_else(|| {
2811                schema_cache.relationship_not_found(
2812                    parent_qi,
2813                    embedded.0,
2814                    embedded.1,
2815                    &parent_qi.schema,
2816                )
2817            })?;
2818
2819        // A resource that yields many rows per parent has no single value to
2820        // order on, so there is nothing the request could mean.
2821        if !rel.is_to_one() {
2822            return Err(postrust_core::Error::RelatedOrderNotPossible {
2823                origin: parent_qi.name.clone(),
2824                relation: relation.clone(),
2825            });
2826        }
2827
2828        let plan = postrust_core::embed::EmbedPlan::resolve(rel, schema_cache)?;
2829
2830        let child_alias = ctx.next_alias();
2831
2832        // The column belongs to the related table, so its type has to be read
2833        // from there. Resolved against the parent it would be unknown, and an
2834        // unknown type is one this process asks the database to render -- for
2835        // a `jsonb` column that would wrap it in `to_jsonb` and then reach
2836        // into the wrapper.
2837        let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
2838            &plan.foreign_schema,
2839            &plan.foreign_table,
2840        );
2841        let mut field = term.field.clone();
2842        field.to_json = !field.json_path.is_empty()
2843            && !schema_cache
2844                .get_table(&child_qi)
2845                .and_then(|table| table.get_column(&field.name))
2846                .is_some_and(|column| matches!(column.data_type.as_str(), "json" | "jsonb"));
2847
2848        let column =
2849            postrust_core::query::QueryBuilder::qualified_column_sql(Some(&child_alias), &field);
2850        let mut expression =
2851            plan.order_expression("src", PARENT_ROW_COLUMN_REF, &child_alias, &column);
2852
2853        match term.direction {
2854            Some(postrust_core::api_request::OrderDirection::Desc) => expression.push_str(" DESC"),
2855            Some(postrust_core::api_request::OrderDirection::Asc) => expression.push_str(" ASC"),
2856            None => {}
2857        }
2858        match term.nulls {
2859            Some(postrust_core::api_request::OrderNulls::First) => {
2860                expression.push_str(" NULLS FIRST")
2861            }
2862            Some(postrust_core::api_request::OrderNulls::Last) => {
2863                expression.push_str(" NULLS LAST")
2864            }
2865            None => {}
2866        }
2867
2868        orders.push(expression);
2869    }
2870
2871    Ok(orders)
2872}
2873
2874/// The relationship an `order=relation(column)` term names, ready to order by.
2875///
2876/// Resolving it is the same work whether the term sits at the top level or
2877/// inside an embed, and it is the same mistake to skip: a relation term
2878/// rendered as a bare column name orders by a column of the wrong table --
2879/// silently, where the parent happens to have a column of that name, and with
2880/// `column "cost" does not exist` where it does not.
2881fn related_order_target(
2882    schema_cache: &postrust_core::SchemaCache,
2883    parent_qi: &postrust_core::api_request::QualifiedIdentifier,
2884    select: &[postrust_core::api_request::SelectItem],
2885    relation: &str,
2886    ctx: &mut EmbedFilters<'_>,
2887) -> Result<
2888    (
2889        postrust_core::embed::EmbedPlan,
2890        String,
2891        postrust_core::api_request::QualifiedIdentifier,
2892    ),
2893    postrust_core::Error,
2894> {
2895    // `order=` names a resource this request embedded, by the name the
2896    // response gives it -- so `pros:projects(*)` is ordered on as `pros`, and
2897    // the table it came from is what the relationship is looked up by. A name
2898    // the selection does not carry is not an embedded resource here, whether
2899    // or not the schema has a table by that name.
2900    let embedded = embedded_by_name(select, relation)
2901        .ok_or_else(|| postrust_core::Error::NotAnEmbeddedResource(relation.to_string()))?;
2902
2903    let rel = schema_cache
2904        .find_relationship(parent_qi, embedded.0, embedded.1, &parent_qi.schema)?
2905        .ok_or_else(|| {
2906            schema_cache.relationship_not_found(
2907                parent_qi,
2908                embedded.0,
2909                embedded.1,
2910                &parent_qi.schema,
2911            )
2912        })?;
2913
2914    // A resource that yields many rows per parent has no single value to order
2915    // on, so there is nothing the request could mean.
2916    if !rel.is_to_one() {
2917        return Err(postrust_core::Error::RelatedOrderNotPossible {
2918            origin: parent_qi.name.clone(),
2919            relation: relation.to_string(),
2920        });
2921    }
2922
2923    let plan = postrust_core::embed::EmbedPlan::resolve(rel, schema_cache)?;
2924    let child_alias = ctx.next_alias();
2925    let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
2926        &plan.foreign_schema,
2927        &plan.foreign_table,
2928    );
2929
2930    Ok((plan, child_alias, child_qi))
2931}
2932
2933/// The `ORDER BY` an embedded resource was asked for, if any.
2934///
2935/// `clients.order=name.desc` orders the rows inside the embed, which is a
2936/// property of the child's own subselect rather than of the parent.
2937/// `tasks.order=projects(id).desc` orders them by a column of a table the
2938/// child embeds in turn -- so it is resolved against the child, and rendered
2939/// as the same correlated subselect the top level uses.
2940#[allow(clippy::too_many_arguments)] // each names one part of where the embed sits
2941fn embed_order_sql(
2942    schema_cache: &postrust_core::SchemaCache,
2943    child_qi: &postrust_core::api_request::QualifiedIdentifier,
2944    child_select: &[postrust_core::api_request::SelectItem],
2945    child_alias: &str,
2946    ctx: &mut EmbedFilters<'_>,
2947    path: &[String],
2948    names: &[String],
2949) -> Result<Option<String>, postrust_core::Error> {
2950    use postrust_core::api_request::{OrderDirection, OrderNulls, OrderTerm};
2951
2952    let terms: Vec<OrderTerm> = ctx
2953        .orders
2954        .iter()
2955        .filter(|(p, _)| p.as_slice() == path || p.as_slice() == names)
2956        .flat_map(|(_, terms)| terms.iter().cloned())
2957        .collect();
2958
2959    if terms.is_empty() {
2960        return Ok(None);
2961    }
2962
2963    let mut rendered = Vec::with_capacity(terms.len());
2964    for term in &terms {
2965        let (field, direction, nulls) = match term {
2966            OrderTerm::Field {
2967                field,
2968                direction,
2969                nulls,
2970            }
2971            | OrderTerm::Relation {
2972                field,
2973                direction,
2974                nulls,
2975                ..
2976            } => (field, direction, nulls),
2977        };
2978
2979        let mut sql = match term {
2980            OrderTerm::Field { .. } => postrust_sql::escape_ident(&field.name),
2981            OrderTerm::Relation { relation, .. } => {
2982                let (plan, grandchild_alias, grandchild_qi) =
2983                    related_order_target(schema_cache, child_qi, child_select, relation, ctx)?;
2984
2985                // The column belongs to the related table, so its type is read
2986                // from there rather than from the child.
2987                let pg_type = schema_cache
2988                    .get_table(&grandchild_qi)
2989                    .and_then(|table| table.get_column(&field.name))
2990                    .map(|column| column.data_type.clone())
2991                    .unwrap_or_default();
2992                let coercible = postrust_core::plan::CoercibleField::from_field(field, &pg_type);
2993                let column = postrust_core::query::QueryBuilder::qualified_column_sql(
2994                    Some(&grandchild_alias),
2995                    &coercible,
2996                );
2997
2998                // The child is a real table alias at this level, so its alias
2999                // is also the expression for its whole row.
3000                plan.order_expression(child_alias, child_alias, &grandchild_alias, &column)
3001            }
3002        };
3003
3004        match direction {
3005            Some(OrderDirection::Desc) => sql.push_str(" DESC"),
3006            Some(OrderDirection::Asc) => sql.push_str(" ASC"),
3007            None => {}
3008        }
3009        match nulls {
3010            Some(OrderNulls::First) => sql.push_str(" NULLS FIRST"),
3011            Some(OrderNulls::Last) => sql.push_str(" NULLS LAST"),
3012            None => {}
3013        }
3014
3015        rendered.push(sql);
3016    }
3017
3018    Ok(Some(rendered.join(", ")))
3019}
3020
3021#[allow(clippy::too_many_arguments)] // each names one part of where the embed sits
3022fn build_embed_expressions(
3023    schema_cache: &postrust_core::SchemaCache,
3024    parent_qi: &postrust_core::api_request::QualifiedIdentifier,
3025    parent_alias: &str,
3026    // The SQL expression yielding the parent's whole row, which a computed
3027    // relationship takes as its argument. At a nested level the parent is a
3028    // real table alias and so is the row; at the top level the parent is a
3029    // derived table, whose alias is a `record` rather than the table's own
3030    // composite type, so a column carrying the row is passed instead.
3031    parent_row: &str,
3032    select: &[postrust_core::api_request::SelectItem],
3033    ctx: &mut EmbedFilters<'_>,
3034    path: &[String],
3035    // The same path spelled with each relation's own name rather than the
3036    // alias the request gave it.
3037    names: &[String],
3038) -> Result<EmbedLevel, postrust_core::Error> {
3039    use postrust_core::api_request::SelectItem;
3040
3041    let mut level = EmbedLevel::default();
3042
3043    for item in select {
3044        // A spread is embedded exactly like a plain relation. The two differ
3045        // only in where the child's columns end up, and that is settled once
3046        // the rows are JSON -- so here the spread is marked by the name its
3047        // expression is given, and flattened afterwards.
3048        let (relation, alias, child_select, join_type, hint, is_spread) = match item {
3049            SelectItem::Relation {
3050                relation,
3051                alias,
3052                select,
3053                join_type,
3054                hint,
3055            } => (relation, alias.clone(), select, join_type, hint, false),
3056            SelectItem::SpreadRelation {
3057                relation,
3058                select,
3059                join_type,
3060                hint,
3061            } => (relation, None, select, join_type, hint, true),
3062            SelectItem::Field { .. } => continue,
3063        };
3064
3065        level.saw_relations = true;
3066
3067        let rel = schema_cache
3068            .find_relationship(parent_qi, relation, hint.as_deref(), &parent_qi.schema)?
3069            .ok_or_else(|| {
3070                schema_cache.relationship_not_found(
3071                    parent_qi,
3072                    relation,
3073                    hint.as_deref(),
3074                    &parent_qi.schema,
3075                )
3076            })?;
3077        let plan = postrust_core::embed::EmbedPlan::resolve(rel, schema_cache)?;
3078
3079        // No row to pass: the caller said the parent's is not expressible
3080        // here, and a computed relationship is a function of it.
3081        if plan.function.is_some() && parent_row.is_empty() {
3082            continue;
3083        }
3084
3085        let child_alias = ctx.next_alias();
3086        let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
3087            &plan.foreign_schema,
3088            &plan.foreign_table,
3089        );
3090
3091        // Filters are addressed by the name the client used, which may be
3092        // either the alias or the relation itself: `c:clients(*)&c.id=eq.1`
3093        // and `the_tasks:tasks(*)&tasks.id=eq.1` both name the same embed.
3094        let mut child_path = path.to_vec();
3095        child_path.push(alias.clone().unwrap_or_else(|| relation.clone()));
3096        let mut child_names = names.to_vec();
3097        child_names.push(relation.clone());
3098        let child_where = ctx.predicate_for(&child_path, &child_names, &child_qi, schema_cache)?;
3099
3100        // Deeper relations first: they become part of this level's SELECT list.
3101        let child_row = postrust_sql::escape_ident(&child_alias);
3102        let nested = build_embed_expressions(
3103            schema_cache,
3104            &child_qi,
3105            &child_alias,
3106            &child_row,
3107            child_select,
3108            ctx,
3109            &child_path,
3110            &child_names,
3111        )?;
3112
3113        // A nested `!inner` is written against this child's alias, which is
3114        // only in scope inside this child's own subselect -- so it narrows the
3115        // child here rather than travelling up to the top-level wrapper.
3116        let child_where = match (child_where, nested.inner_joins.is_empty()) {
3117            (existing, true) => existing,
3118            (Some(existing), false) => Some(format!(
3119                "{} AND {}",
3120                existing,
3121                nested.inner_joins.join(" AND ")
3122            )),
3123            (None, false) => Some(nested.inner_joins.join(" AND ")),
3124        };
3125
3126        // `!inner` on this relation restricts the parent rows. It is emitted
3127        // against `parent_alias`, so it belongs to the caller's level; reusing
3128        // the same predicate string lets both places share placeholders.
3129        // The child keeps the same alias it has in the embed subselect. The
3130        // two are sibling scopes, never nested, so there is no collision --
3131        // and any nested predicate folded into `child_where` already names
3132        // that alias, so it has to be the one in scope here too.
3133        if matches!(join_type, Some(postrust_core::api_request::JoinType::Inner)) {
3134            level.inner_joins.push(plan.inner_join_predicate(
3135                parent_alias,
3136                parent_row,
3137                &child_alias,
3138                child_where.as_deref(),
3139            ));
3140        }
3141
3142        level.spread_columns.extend(nested.spread_columns);
3143        let nested_expressions = nested.expressions;
3144
3145        let child_table =
3146            schema_cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3147                &plan.foreign_schema,
3148                &plan.foreign_table,
3149            ));
3150
3151        // The child's own columns. Empty means every column, which is what a
3152        // relation with no explicit selection asks for.
3153        let mut parts: Vec<String> = Vec::new();
3154        let mut project_everything = child_select.is_empty();
3155        for nested_item in child_select {
3156            match nested_item {
3157                SelectItem::Field { field, .. } if field.name == "*" => {
3158                    project_everything = true;
3159                }
3160                SelectItem::Field { field, alias, .. } => {
3161                    let column = postrust_sql::escape_ident(&field.name);
3162                    // A schema that declared how one of its domains is written
3163                    // on the wire meant it wherever the value appears, and an
3164                    // embedded row is the same value in a different place.
3165                    let rendered = child_table
3166                        .as_ref()
3167                        .and_then(|table| table.get_column(&field.name))
3168                        .and_then(|c| schema_cache.representation(c.representation_type(), "json"))
3169                        .map(|function| format!("{}({})", function, column))
3170                        .unwrap_or_else(|| column.clone());
3171                    let name = alias.as_deref().unwrap_or(&field.name);
3172                    parts.push(format!(
3173                        "{} AS {}",
3174                        rendered,
3175                        postrust_sql::escape_ident(name)
3176                    ));
3177                }
3178                // Handled as an expression, not a column.
3179                SelectItem::Relation { .. } | SelectItem::SpreadRelation { .. } => {}
3180            }
3181        }
3182        if project_everything {
3183            parts.clear();
3184            parts.push(format!("{}.*", postrust_sql::escape_ident(&child_alias)));
3185        }
3186        for (field_name, expression) in nested_expressions {
3187            parts.push(format!(
3188                "{} AS {}",
3189                expression,
3190                postrust_sql::escape_ident(&field_name)
3191            ));
3192        }
3193
3194        let inner_select = parts.join(", ");
3195        let child_order = embed_order_sql(
3196            schema_cache,
3197            &child_qi,
3198            child_select,
3199            &child_alias,
3200            ctx,
3201            &child_path,
3202            &child_names,
3203        )?;
3204        let (child_limit, child_offset) = ctx.range_for(&child_path, &child_names);
3205        let expression = plan.embed_expression(
3206            parent_alias,
3207            parent_row,
3208            &child_alias,
3209            &inner_select,
3210            child_limit,
3211            child_offset,
3212            child_where.as_deref(),
3213            child_order.as_deref(),
3214            // Positionally, with the parent row and nothing else: this surface
3215            // has no session to supply and no place to write an argument.
3216            None,
3217            ctx.rendering,
3218        )?;
3219
3220        if is_spread {
3221            let names = spread_output_names(schema_cache, &child_qi, child_select);
3222            if names.is_empty() {
3223                // `...clients()` spreads no columns, so there is nothing to
3224                // fetch and nothing to merge.
3225                continue;
3226            }
3227            level
3228                .spread_columns
3229                .push((format!("{}{}", SPREAD_KEY_PREFIX, child_alias), names));
3230        }
3231
3232        let key = match is_spread {
3233            // A name nothing can collide with, since the object it names is
3234            // dissolved into its parent before anyone sees it.
3235            true => format!("{}{}", SPREAD_KEY_PREFIX, child_alias),
3236            false => alias.clone().unwrap_or_else(|| relation.clone()),
3237        };
3238
3239        // Under the name the client used, whatever the response key is: a
3240        // filter naming the embed is written against that name.
3241        //
3242        // What it records is an existence test, not the expression. Asking
3243        // whether a to-many embed is null would never be true -- it renders as
3244        // `[]`, never as null -- and "did this match anything" is the question
3245        // either way.
3246        level.filterable.push((
3247            alias.clone().unwrap_or_else(|| relation.clone()),
3248            plan.inner_join_predicate(
3249                parent_alias,
3250                parent_row,
3251                &child_alias,
3252                child_where.as_deref(),
3253            ),
3254        ));
3255
3256        // `clients()` asks for none of the related resource's columns. It is
3257        // written to narrow the parent -- with `!inner`, or with a filter on
3258        // the embed -- and the resource itself is not part of the answer, so
3259        // it gets no key at all rather than an empty object. The test is
3260        // recursive: an embed whose only content is such an embed contributes
3261        // nothing either.
3262        if !yields_columns(child_select) {
3263            continue;
3264        }
3265
3266        level.expressions.push((key, expression));
3267    }
3268
3269    Ok(level)
3270}
3271
3272/// The media type a function's own return type declares, where the client
3273/// asked for it.
3274///
3275/// A function returning a domain named `text/plain` returns text, and the
3276/// value is the whole response. It is still only returned that way to a client
3277/// that asked: the same value serialised as JSON is a perfectly good answer to
3278/// a request that did not.
3279fn declared_media_type(
3280    db_plan: &DbActionPlan,
3281    api_request: &postrust_core::api_request::ApiRequest,
3282) -> Option<(String, String)> {
3283    let DbActionPlan::Call { call, .. } = db_plan else {
3284        return None;
3285    };
3286    let declared = call.media_type.as_deref()?;
3287    let base = call.media_base_type.clone().unwrap_or_default();
3288    // A function declaring `*/*` says it will render whatever was asked for,
3289    // so every request is one it answers -- and what it answers with is bytes,
3290    // which on the wire is called `application/octet-stream`.
3291    if declared == "*/*" {
3292        return Some(("application/octet-stream".to_string(), base));
3293    }
3294    api_request
3295        .accept_media_types
3296        .iter()
3297        .any(|accepted| accepted.content_type() == declared)
3298        .then(|| (declared.to_string(), base))
3299}
3300
3301/// Whether a selection produces any keys in the response.
3302///
3303/// A relation contributes only what its own selection does, so `tasks()` and
3304/// `tasks(projects())` are alike empty, while `tasks(name)` is not.
3305fn yields_columns(select: &[postrust_core::api_request::SelectItem]) -> bool {
3306    use postrust_core::api_request::SelectItem;
3307
3308    select.iter().any(|item| match item {
3309        SelectItem::Field { .. } => true,
3310        SelectItem::Relation { select, .. } | SelectItem::SpreadRelation { select, .. } => {
3311            yields_columns(select)
3312        }
3313    })
3314}
3315
3316type EmbedFuture<'f> = std::pin::Pin<
3317    Box<dyn std::future::Future<Output = Result<(), postrust_core::Error>> + Send + 'f>,
3318>;
3319
3320/// Attach the relations requested in `select` onto `rows`.
3321///
3322/// One query per relation per level: the parents' join keys are collected and
3323/// passed as a single array, so embedding across a page of parents does not
3324/// become a query per row. Recurses for nested embeds.
3325fn embed_relations<'f>(
3326    conn: &'f mut sqlx::PgConnection,
3327    schema_cache: &'f postrust_core::SchemaCache,
3328    parent_qi: &'f postrust_core::api_request::QualifiedIdentifier,
3329    select: &'f [postrust_core::api_request::SelectItem],
3330    rows: &'f mut [serde_json::Value],
3331    max_rows: Option<i64>,
3332) -> EmbedFuture<'f> {
3333    use postrust_core::api_request::SelectItem;
3334
3335    Box::pin(async move {
3336        if rows.is_empty() {
3337            return Ok(());
3338        }
3339
3340        for item in select {
3341            // A spread embed is fetched exactly like a plain one -- the two
3342            // differ only in where the child's columns end up, which is
3343            // settled once the rows are in hand.
3344            let (relation, alias, nested, is_spread, hint) = match item {
3345                SelectItem::Relation {
3346                    relation,
3347                    alias,
3348                    select: nested,
3349                    hint,
3350                    ..
3351                } => (relation, alias.clone(), nested, false, hint),
3352                SelectItem::SpreadRelation {
3353                    relation,
3354                    select: nested,
3355                    hint,
3356                    ..
3357                } => (relation, None, nested, true, hint),
3358                SelectItem::Field { .. } => continue,
3359            };
3360
3361            let rel = schema_cache
3362                .find_relationship(parent_qi, relation, hint.as_deref(), &parent_qi.schema)?
3363                .ok_or_else(|| {
3364                    schema_cache.relationship_not_found(
3365                        parent_qi,
3366                        relation,
3367                        hint.as_deref(),
3368                        &parent_qi.schema,
3369                    )
3370                })?;
3371
3372            let plan = postrust_core::embed::EmbedPlan::resolve(rel, schema_cache)?;
3373            let keys = postrust_core::embed::parent_keys(rows, &plan.local_column);
3374
3375            // Which of the child's columns the query needs to return.
3376            //
3377            // A column that was not asked for still costs a heap read, JSON
3378            // serialisation in PostgreSQL, socket bytes and a parse before
3379            // being discarded, so the projection is worked out before the query
3380            // rather than filtered afterwards.
3381            //
3382            // A nested embed joins on a column of the child row, so that column
3383            // has to survive the projection even when the client did not ask
3384            // for it. A spread relation falls back to every column: its shape
3385            // is decided further down and is not a plain projection.
3386            let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
3387                &plan.foreign_schema,
3388                &plan.foreign_table,
3389            );
3390
3391            let mut child_columns: Vec<String> = Vec::new();
3392            let mut project_everything = nested.is_empty();
3393            for nested_item in nested {
3394                match nested_item {
3395                    SelectItem::Field { field, .. } => child_columns.push(field.name.clone()),
3396                    SelectItem::Relation { relation, .. } => {
3397                        match schema_cache
3398                            .find_relationship(&child_qi, relation, None, &child_qi.schema)
3399                            .ok()
3400                            .flatten()
3401                        {
3402                            Some(nested_rel) => {
3403                                let nested_plan = postrust_core::embed::EmbedPlan::resolve(
3404                                    nested_rel,
3405                                    schema_cache,
3406                                )?;
3407                                child_columns.push(nested_plan.local_column);
3408                            }
3409                            // Leave it to the recursive call to report the
3410                            // unknown relation, rather than failing here with
3411                            // less context.
3412                            None => project_everything = true,
3413                        }
3414                    }
3415                    SelectItem::SpreadRelation { .. } => project_everything = true,
3416                }
3417            }
3418            if project_everything {
3419                child_columns.clear();
3420            }
3421
3422            let mut grouped = if keys.is_empty() {
3423                std::collections::HashMap::new()
3424            } else {
3425                let sql = plan.children_grouped_sql(max_rows, &child_columns)?;
3426
3427                let fetched = sqlx::query(&sql)
3428                    .bind(&keys)
3429                    .fetch_all(&mut *conn)
3430                    .await
3431                    .map_err(map_sqlx_error)?;
3432
3433                // The query returns the join key and a JSON array of that
3434                // key's children, so the values are read straight out of the
3435                // columns -- the typed row converter would wrap them under the
3436                // expression name.
3437                use sqlx::Row;
3438                let pairs: Vec<(serde_json::Value, serde_json::Value)> = fetched
3439                    .into_iter()
3440                    .filter_map(|row| {
3441                        Some((
3442                            row.try_get::<serde_json::Value, _>(0).ok()?,
3443                            row.try_get::<serde_json::Value, _>(1).ok()?,
3444                        ))
3445                    })
3446                    .collect();
3447
3448                postrust_core::embed::group_from_aggregated(pairs)
3449            };
3450
3451            // Deeper embeds, if any were asked for.
3452            //
3453            // A nested embed issues one query for every child row at this
3454            // level, so the rows have to be contiguous for it. They arrive
3455            // grouped, so they are flattened for the recursive call and put
3456            // back afterwards. When nothing deeper was requested this is all
3457            // skipped, which is the common case.
3458            let has_deeper_embed = nested.iter().any(|item| {
3459                matches!(
3460                    item,
3461                    SelectItem::Relation { .. } | SelectItem::SpreadRelation { .. }
3462                )
3463            });
3464
3465            if has_deeper_embed {
3466                let mut order: Vec<(String, usize)> = Vec::with_capacity(grouped.len());
3467                let mut flat: Vec<serde_json::Value> = Vec::new();
3468                for (key, children) in grouped.drain() {
3469                    order.push((key, children.len()));
3470                    flat.extend(children);
3471                }
3472
3473                embed_relations(
3474                    &mut *conn,
3475                    schema_cache,
3476                    &child_qi,
3477                    nested,
3478                    &mut flat,
3479                    max_rows,
3480                )
3481                .await?;
3482
3483                let mut rest = flat.into_iter();
3484                for (key, count) in order {
3485                    grouped.insert(key, rest.by_ref().take(count).collect());
3486                }
3487            }
3488
3489            // Return only the requested columns of the related resource. An
3490            // empty nested select means every column.
3491            //
3492            // The projection in the query covers the columns; this covers what
3493            // is left over, which is the join column when the client did not
3494            // ask for it.
3495            // A spread picks its own columns out when it merges them, and does
3496            // it by the child's own column names, so pruning here would only
3497            // get in the way.
3498            let requested: Option<std::collections::HashSet<String>> =
3499                if nested.is_empty() || is_spread {
3500                    None
3501                } else {
3502                    Some(
3503                        nested
3504                            .iter()
3505                            .flat_map(|nested_item| match nested_item {
3506                                SelectItem::Field { field, alias, .. } => {
3507                                    vec![alias.clone().unwrap_or_else(|| field.name.clone())]
3508                                }
3509                                SelectItem::Relation {
3510                                    relation, alias, ..
3511                                } => vec![alias.clone().unwrap_or_else(|| relation.clone())],
3512                                // A spread one level down has already merged
3513                                // its columns into these rows, so they are
3514                                // part of what was asked for.
3515                                SelectItem::SpreadRelation { select, .. } => select
3516                                    .iter()
3517                                    .filter_map(|column| match column {
3518                                        SelectItem::Field { field, .. } => Some(field.name.clone()),
3519                                        _ => None,
3520                                    })
3521                                    .collect(),
3522                            })
3523                            .collect(),
3524                    )
3525                };
3526
3527            if let Some(requested) = requested {
3528                for group in grouped.values_mut() {
3529                    for child in group.iter_mut() {
3530                        if let Some(object) = child.as_object_mut() {
3531                            object.retain(|key, _| requested.contains(key));
3532                        }
3533                    }
3534                }
3535            }
3536            if is_spread {
3537                // The child rows carry the table's own column names, so a
3538                // column is read by its real name and written under its alias.
3539                let spread_columns: Vec<(String, String)> = nested
3540                    .iter()
3541                    .filter_map(|nested_item| match nested_item {
3542                        SelectItem::Field { field, alias, .. } => Some((
3543                            field.name.clone(),
3544                            alias.clone().unwrap_or_else(|| field.name.clone()),
3545                        )),
3546                        _ => None,
3547                    })
3548                    .collect();
3549                for row in rows.iter_mut() {
3550                    postrust_core::embed::spread_into_parent(row, &plan, &grouped, &spread_columns);
3551                }
3552            } else {
3553                let field_name = alias.clone().unwrap_or_else(|| relation.clone());
3554                for row in rows.iter_mut() {
3555                    postrust_core::embed::attach_to_parent(row, &field_name, &plan, &grouped);
3556                }
3557            }
3558        }
3559
3560        Ok(())
3561    })
3562}
3563
3564/// Bind SqlParam values to a sqlx query.
3565fn bind_params<'q>(
3566    mut query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
3567    params: &'q [postrust_sql::SqlParam],
3568) -> sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments> {
3569    use postrust_sql::SqlParam;
3570
3571    for param in params {
3572        query = match param {
3573            SqlParam::Null => query.bind(None::<String>),
3574            SqlParam::Bool(b) => query.bind(b),
3575            SqlParam::Int(n) => query.bind(n),
3576            SqlParam::Float(f) => query.bind(f),
3577            SqlParam::Text(s) => query.bind(s),
3578            SqlParam::Bytes(b) => query.bind(b),
3579            SqlParam::Json(j) => query.bind(j),
3580            SqlParam::Uuid(u) => query.bind(u),
3581            SqlParam::Timestamp(t) => query.bind(t),
3582            SqlParam::Array(arr) => {
3583                // Convert array to Vec<String> for text arrays
3584                let strings: Vec<String> = arr
3585                    .iter()
3586                    .map(|p| match p {
3587                        SqlParam::Text(s) => s.clone(),
3588                        SqlParam::Int(n) => n.to_string(),
3589                        SqlParam::Bool(b) => b.to_string(),
3590                        other => format!("{:?}", other),
3591                    })
3592                    .collect();
3593                query.bind(strings)
3594            }
3595        };
3596    }
3597
3598    query
3599}
3600
3601/// Map sqlx error to our error type.
3602fn map_sqlx_error(e: sqlx::Error) -> postrust_core::Error {
3603    match e {
3604        sqlx::Error::Database(db_err) => {
3605            // Try to downcast to Postgres-specific error for additional details
3606            let (details, hint) = db_err
3607                .try_downcast_ref::<sqlx::postgres::PgDatabaseError>()
3608                .map(|pg_err| {
3609                    (
3610                        pg_err.detail().map(String::from),
3611                        pg_err.hint().map(String::from),
3612                    )
3613                })
3614                .unwrap_or((None, None));
3615
3616            postrust_core::Error::Database(postrust_core::error::DatabaseError {
3617                code: db_err.code().map(|c| c.to_string()).unwrap_or_default(),
3618                message: db_err.message().to_string(),
3619                details,
3620                hint,
3621                constraint: db_err.constraint().map(|s| s.to_string()),
3622                table: db_err.table().map(|s| s.to_string()),
3623                column: None,
3624            })
3625        }
3626        other => postrust_core::Error::Internal(other.to_string()),
3627    }
3628}
3629
3630/// Build an HTTP response from our response type.
3631fn build_response(response: PgrstResponse) -> Response {
3632    let mut builder = Response::builder().status(response.status);
3633
3634    for (key, value) in &response.headers {
3635        builder = builder.header(key, value);
3636    }
3637
3638    builder
3639        .body(Body::from(response.body))
3640        .unwrap_or_else(|_| Response::new(Body::empty()))
3641}
3642
3643/// Build an error response.
3644///
3645/// In production mode (PGRST_DEBUG=false or unset), sensitive error details
3646/// are hidden to prevent information leakage.
3647/// Build the response for a failed request.
3648///
3649/// `verbatim_db_errors` passes a database failure through as PostgreSQL
3650/// reported it -- its SQLSTATE, message, detail and hint -- which is what
3651/// PostgREST does and what a client written against PostgREST branches on.
3652/// It is off unless compatibility mode is on, because those fields describe
3653/// the schema rather than the request: a constraint's name, a column that was
3654/// not selected, the text of a failing query. Everything else is reported the
3655/// same either way.
3656fn error_response(error: postrust_core::Error, verbatim_db_errors: bool) -> Response {
3657    let status = error.status_code();
3658
3659    // Check if debug mode is enabled
3660    let debug_mode = std::env::var("PGRST_DEBUG")
3661        .map(|v| v == "true" || v == "1")
3662        .unwrap_or(false);
3663
3664    // A function that raised `sqlstate 'PGRST'` supplied the whole response:
3665    // its status, its headers and its body. Nothing here is the API layer's to
3666    // decide, including whether to sanitise it -- the schema wrote it.
3667    if let postrust_core::Error::Database(db) = &error {
3668        match db.raised_response() {
3669            Some(Ok(raised)) => {
3670                let mut builder = Response::builder()
3671                    .status(status)
3672                    .header("content-type", "application/json");
3673                for (name, value) in &raised.headers {
3674                    builder = builder.header(name, value);
3675                }
3676                return builder
3677                    .body(Body::from(
3678                        serde_json::to_vec(&raised.body).unwrap_or_default(),
3679                    ))
3680                    .unwrap_or_else(|_| Response::new(Body::empty()));
3681            }
3682            // The schema meant to write a response and wrote something else.
3683            // Reported as the fault it is, and reported the same way whether
3684            // or not database errors are passed through, since this one is
3685            // about the schema's own text rather than about the request.
3686            Some(Err(fault)) => {
3687                return error_response(
3688                    postrust_core::Error::RaiseNotUnderstood(fault),
3689                    verbatim_db_errors,
3690                )
3691            }
3692            None => {}
3693        }
3694    }
3695
3696    let body = if debug_mode {
3697        // Full error details in debug mode
3698        serde_json::to_vec(&error.to_json()).unwrap_or_default()
3699    } else if let (true, postrust_core::Error::Database(db)) = (verbatim_db_errors, &error) {
3700        serde_json::to_vec(&serde_json::json!({
3701            "code": db.code,
3702            "message": db.message,
3703            "details": db.details,
3704            "hint": db.hint,
3705        }))
3706        .unwrap_or_default()
3707    } else {
3708        // Sanitized error in production
3709        let sanitized = serde_json::json!({
3710            "code": error.code(),
3711            "message": sanitize_error_message(&error),
3712            "details": error.details(),
3713            // A hint says how to correct the request. It is carried by the
3714            // error itself, so it never has to reach for anything the
3715            // sanitised message deliberately left out.
3716            "hint": error.hint(),
3717        });
3718        serde_json::to_vec(&sanitized).unwrap_or_default()
3719    };
3720
3721    let mut builder = Response::builder()
3722        .status(status)
3723        .header("content-type", "application/json");
3724
3725    // A 401 has to say what would satisfy it, or a client has no way to know
3726    // it should be sending a token at all. Where a token *was* sent and was
3727    // not accepted, the challenge says which -- `invalid_token` with the
3728    // reason, so a client can tell "your key is wrong" from "your clock is"
3729    // without parsing the body.
3730    if status == StatusCode::UNAUTHORIZED {
3731        let challenge = match &error {
3732            postrust_core::Error::InvalidJwt(_)
3733            | postrust_core::Error::NoSuitableJwtKey
3734            | postrust_core::Error::JwtClaim(_) => format!(
3735                "Bearer error=\"invalid_token\", error_description=\"{}\"",
3736                error
3737            ),
3738            _ => "Bearer".to_string(),
3739        };
3740        builder = builder.header("www-authenticate", challenge);
3741    }
3742
3743    builder
3744        .body(Body::from(body))
3745        .unwrap_or_else(|_| Response::new(Body::empty()))
3746}
3747
3748/// Sanitize error messages for production.
3749fn sanitize_error_message(error: &postrust_core::Error) -> String {
3750    use postrust_core::Error;
3751
3752    // A schema-cache miss names what the client asked for, and the client
3753    // already knows that -- it is what it sent. Saying so is what makes the
3754    // difference between "Resource not found" and a message the client can act
3755    // on, and it discloses nothing it did not supply. What stays sanitised is
3756    // the database's own account of a failure, which is about the schema
3757    // rather than about the request.
3758    match error {
3759        Error::TableNotFound { name, .. } | Error::NotFound(name) => {
3760            return format!("Could not find the table '{}' in the schema cache", name)
3761        }
3762        Error::FunctionNotFound {
3763            name,
3764            params,
3765            single_param,
3766            ..
3767        } => {
3768            // A body that is one value carries no argument names, so there is
3769            // no argument list to report -- naming the function is the whole
3770            // of what was looked for.
3771            let looked_for =
3772                match postrust_core::error::names_only_one_param(single_param.as_deref()) {
3773                    true => name.clone(),
3774                    false => postrust_core::error::function_signature(name, params),
3775                };
3776            return format!(
3777                "Could not find the function {} in the schema cache",
3778                looked_for
3779            );
3780        }
3781        Error::UnacceptableSchema { requested, .. } => {
3782            return format!("Invalid schema: {}", requested)
3783        }
3784        // The candidate list is the whole point of the message: it names the
3785        // signatures that could not be told apart.
3786        Error::AmbiguousFunction { .. } | Error::AmbiguousRelationship { .. } => {
3787            // The candidates are the whole point of these messages: they name
3788            // what could not be told apart.
3789            return error.to_string();
3790        }
3791        _ => {}
3792    }
3793
3794    (match error {
3795        Error::ColumnNotFound(_) => "Column not found",
3796        // The column is the client's own word and the relation is the one it
3797        // addressed, so naming both says nothing it did not send.
3798        Error::UnknownColumn { .. } => return error.to_string(),
3799        // Each of these says only what the request itself said: the resource
3800        // it named, the preference it sent, the shape it asked for. Repeating
3801        // that back tells the client nothing it did not already know, and is
3802        // the difference between a message it can act on and one it cannot.
3803        Error::RelationshipNotFound { .. }
3804        | Error::NotAnEmbeddedResource(_)
3805        | Error::RelatedOrderNotPossible { .. }
3806        | Error::InvalidPreferences(_)
3807        | Error::NotSingular { .. }
3808        | Error::InvalidRange(_)
3809        | Error::InvalidBody(_)
3810        | Error::UnsupportedMethod(_)
3811        | Error::InvalidRpcMethod(_)
3812        | Error::InvalidPutFilters
3813        | Error::PutLimitNotAllowed
3814        | Error::PutMatchingPk
3815        | Error::MaxAffectedExceeded(_)
3816        | Error::InvalidGucHeaders
3817        | Error::InvalidGucStatus
3818        | Error::InvalidMediaType(_)
3819        // Says only that the rows the request asked for carry no geometry,
3820        // which is about the request's own shape and is what the client has
3821        // to change.
3822        | Error::MissingGeometryColumn
3823        // Quotes the client's own URL back at it, and names the grammar the
3824        // API publishes. Neither is the schema's.
3825        | Error::UnparsableQuery { .. }
3826        // Says nothing about the schema beyond that one RAISE is malformed,
3827        // which is what the author has to fix.
3828        | Error::RaiseNotUnderstood(_)
3829        | Error::InvalidResourcePath => return error.to_string(),
3830        Error::InvalidPath(_) => "Invalid request path",
3831        // What the token failed on is the client's own token, so naming it
3832        // costs nothing and is the only way it can tell a bad signature from
3833        // an expired one.
3834        Error::InvalidJwt(_)
3835        | Error::NoSuitableJwtKey
3836        | Error::JwtClaim(_)
3837        | Error::MissingAuth => return error.to_string(),
3838        Error::InsufficientPermissions(_) => "Forbidden",
3839
3840        // A fixed policy message that reveals nothing about the request or the
3841        // schema, so it is passed through verbatim -- and it is what PostgREST
3842        // says, which is what a client branching on it will be matching.
3843        Error::AggregatesNotAllowed => "Use of aggregate functions is not allowed",
3844        Error::InvalidHeader(_) | Error::InvalidQueryParam(_) => "Invalid request",
3845        Error::Database(_) => "Database error",
3846        Error::ConnectionPool(_) => "Service temporarily unavailable",
3847        Error::Internal(_) => "Internal server error",
3848        _ => "An error occurred",
3849    })
3850    .to_string()
3851}
3852
3853#[cfg(test)]
3854mod tests {
3855    /// The REST surface is mounted at `/api`, and an `OPTIONS` reaches the
3856    /// layer before the mount is stripped off.
3857    #[test]
3858    fn the_api_path_is_the_one_below_the_mount() {
3859        assert_eq!(super::api_path("/api/items", false), Some("/items"));
3860        assert_eq!(super::api_path("/api/rpc/f", false), Some("/rpc/f"));
3861        assert_eq!(super::api_path("/api", false), Some("/"));
3862        assert_eq!(super::api_path("/api/", false), Some("/"));
3863    }
3864
3865    /// Answering for a route that is not the API's turns a working preflight
3866    /// into a 404 for a table nobody asked about.
3867    #[test]
3868    fn a_route_that_is_not_the_api_is_left_alone() {
3869        for path in [
3870            "/admin",
3871            "/admin/swagger",
3872            "/_",
3873            "/_/health",
3874            "/api/graphql",
3875            // The endpoints a Hasura deployment has. `/v1/graphql` is the one
3876            // every generated client is pointed at, so answering its preflight
3877            // with "no such table" breaks the surface this branch exists to
3878            // offer -- and only in compatibility mode, where nothing else
3879            // looks wrong.
3880            "/healthz",
3881            "/v1/version",
3882            "/v1/graphql",
3883            "/v1/graphql/ws",
3884            "/v1alpha1/graphql",
3885        ] {
3886            assert_eq!(super::api_path(path, false), None, "{path}");
3887            assert_eq!(super::api_path(path, true), None, "{path}");
3888        }
3889    }
3890
3891    /// The mounts are whole segments here too: a table may be called `v1`.
3892    #[test]
3893    fn a_hasura_mount_does_not_swallow_a_table_beside_it() {
3894        assert_eq!(super::api_path("/v1", true), Some("/v1"));
3895        assert_eq!(super::api_path("/healthzz", true), Some("/healthzz"));
3896        assert_eq!(super::api_path("/v1alpha1", true), Some("/v1alpha1"));
3897    }
3898
3899    /// Outside the mounts the surface exists only in compatibility mode,
3900    /// where PostgREST's own paths answer at the root.
3901    #[test]
3902    fn the_root_is_the_api_only_in_compatibility_mode() {
3903        assert_eq!(super::api_path("/items", true), Some("/items"));
3904        assert_eq!(super::api_path("/", true), Some("/"));
3905        assert_eq!(super::api_path("/items", false), None);
3906        assert_eq!(super::api_path("/", false), None);
3907    }
3908
3909    /// A mount is a path segment: `/apiary` is not under `/api`.
3910    #[test]
3911    fn a_mount_matches_whole_segments_only() {
3912        assert_eq!(super::api_path("/apiary", true), Some("/apiary"));
3913        assert_eq!(
3914            super::api_path("/administrators", true),
3915            Some("/administrators")
3916        );
3917    }
3918
3919    /// A data-modifying `WITH` may only appear at the top level, so anything
3920    /// that wraps the statement has to leave the clause where it is.
3921    #[test]
3922    fn a_leading_cte_is_lifted_off_the_statement() {
3923        let (clause, body) = super::split_leading_cte(
3924            "WITH pgrst_mutation_result AS (DELETE FROM t WHERE n = ')' RETURNING t.id) \
3925             SELECT \"id\" FROM \"pgrst_mutation_result\"",
3926        );
3927
3928        assert_eq!(
3929            clause,
3930            "WITH pgrst_mutation_result AS (DELETE FROM t WHERE n = ')' RETURNING t.id) "
3931        );
3932        assert_eq!(body, "SELECT \"id\" FROM \"pgrst_mutation_result\"");
3933    }
3934
3935    /// A plain read has no clause to lift.
3936    #[test]
3937    fn a_statement_without_a_cte_is_left_alone() {
3938        let sql = "SELECT \"id\" FROM \"test\".\"items\"";
3939        let (clause, body) = super::split_leading_cte(sql);
3940
3941        assert!(clause.is_empty());
3942        assert_eq!(body, sql);
3943    }
3944
3945    use super::*;
3946    use postrust_core::plan::CallParams;
3947    use postrust_core::QualifiedIdentifier;
3948    use serde_json::json;
3949
3950    fn call_plan(name: &str, returns_set: bool) -> CallPlan {
3951        CallPlan {
3952            output_columns: Vec::new(),
3953            media_type: None,
3954            media_base_type: None,
3955            function: QualifiedIdentifier::new("public", name),
3956            params: CallParams::None,
3957            returns_scalar: !returns_set,
3958            return_type: None,
3959            returns_set,
3960            returns_composite: false,
3961            volatility: "Volatile".into(),
3962            param_types: Vec::new(),
3963            returns_void: false,
3964            variadic_params: Vec::new(),
3965        }
3966    }
3967
3968    fn composite_call_plan(name: &str, returns_set: bool) -> CallPlan {
3969        CallPlan {
3970            returns_composite: true,
3971            returns_scalar: false,
3972            return_type: None,
3973            ..call_plan(name, returns_set)
3974        }
3975    }
3976
3977    #[test]
3978    fn unwraps_json_return_to_bare_object() {
3979        // `SELECT * FROM sync(...)` on a json-returning function yields a single
3980        // column named after the function.
3981        let rows = vec![json!({"sync": {"ok": true, "count": 3}})];
3982        let (rows, singular) = unwrap_rpc_rows(rows, &call_plan("sync", false));
3983        assert!(singular, "non-set-returning function should be singular");
3984        assert_eq!(rows, vec![json!({"ok": true, "count": 3})]);
3985    }
3986
3987    #[test]
3988    fn unwraps_scalar_return() {
3989        let rows = vec![json!({"add": 42})];
3990        let (rows, singular) = unwrap_rpc_rows(rows, &call_plan("add", false));
3991        assert!(singular);
3992        assert_eq!(rows, vec![json!(42)]);
3993    }
3994
3995    #[test]
3996    fn unwraps_setof_scalar_to_array() {
3997        let rows = vec![json!({"gen": 1}), json!({"gen": 2})];
3998        let (rows, singular) = unwrap_rpc_rows(rows, &call_plan("gen", true));
3999        assert!(!singular, "set-returning function should not be singular");
4000        assert_eq!(rows, vec![json!(1), json!(2)]);
4001    }
4002
4003    #[test]
4004    fn leaves_multi_column_rows_untouched() {
4005        // `RETURNS TABLE(...)` / composite set: rows already have real columns
4006        // and must not be un-nested.
4007        let rows = vec![json!({"id": 1, "name": "a"}), json!({"id": 2, "name": "b"})];
4008        let (out, singular) =
4009            unwrap_rpc_rows(rows.clone(), &composite_call_plan("list_users", true));
4010        assert!(!singular);
4011        assert_eq!(out, rows);
4012    }
4013
4014    #[test]
4015    fn leaves_table_column_named_like_function_untouched() {
4016        // `CREATE FUNCTION foo() RETURNS TABLE(foo int)`: the single output
4017        // column legitimately shares the function's name. The composite
4018        // return-type metadata must prevent it from being mistaken for the
4019        // function-name wrapper.
4020        let rows = vec![json!({"foo": 1}), json!({"foo": 2})];
4021        let (out, singular) = unwrap_rpc_rows(rows.clone(), &composite_call_plan("foo", true));
4022        assert!(!singular);
4023        assert_eq!(out, rows);
4024    }
4025
4026    #[test]
4027    fn single_composite_return_is_singular_but_not_unwrapped() {
4028        // A non-set function returning a row type expands to its columns;
4029        // nothing to un-nest, but the result still renders as a bare object.
4030        let rows = vec![json!({"id": 1, "name": "a"})];
4031        let (out, singular) =
4032            unwrap_rpc_rows(rows.clone(), &composite_call_plan("get_user", false));
4033        assert!(singular);
4034        assert_eq!(out, rows);
4035    }
4036
4037    #[test]
4038    fn leaves_single_key_row_untouched_when_key_is_not_function_name() {
4039        // A single real column that happens to be the only column should not be
4040        // mistaken for the function-name wrapper.
4041        let rows = vec![json!({"id": 7})];
4042        let (out, _) = unwrap_rpc_rows(rows.clone(), &call_plan("get_thing", false));
4043        assert_eq!(out, rows);
4044    }
4045}