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