Skip to main content

postrust_engine/
handler.rs

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