Skip to main content

postrust_graphql/
handler.rs

1//! Axum handler for the /graphql endpoint.
2//!
3//! Provides GraphQL request handling using async-graphql with dynamic schema
4//! generation from the PostgreSQL schema cache.
5
6use crate::context::GraphQLContext;
7use crate::error::GraphQLError;
8use crate::schema::object::TableObjectType;
9use crate::schema::relationship::RelationshipField;
10use crate::schema::{build_schema, FederationEntity, GeneratedSchema, MutationType, SchemaConfig};
11use crate::subscription::{
12    generate_subscription_fields, NotifyBroker, SubscriptionField as SubField,
13};
14use async_graphql::dynamic::*;
15use async_graphql::extensions::{
16    Extension, ExtensionContext, ExtensionFactory, NextResolve, ResolveInfo,
17};
18use async_graphql::parser::types::{FragmentDefinition, Selection, SelectionSet, TypeCondition};
19use async_graphql::{Name, Positioned, SelectionField, ServerResult, Value};
20use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
21use axum::extract::State;
22use axum::response::IntoResponse;
23use postrust_core::schema_cache::SchemaCache;
24use sqlx::{AssertSqlSafe, PgPool};
25use std::collections::{BTreeMap, HashMap, HashSet};
26use std::sync::Arc;
27use tokio::sync::RwLock;
28use tracing::{debug, info, trace};
29
30/// GraphQL execution state shared across requests.
31pub struct GraphQLState {
32    /// Database connection pool
33    pub pool: PgPool,
34    /// Schema cache
35    pub schema_cache: Arc<SchemaCache>,
36    /// Generated GraphQL schema
37    pub generated_schema: GeneratedSchema,
38    /// async-graphql Schema (built dynamically)
39    ///
40    /// The unrestricted one: what an administrator is answered from, and what
41    /// every caller is answered from on a server with no permission document.
42    pub schema: Schema,
43    /// One schema per role the permission document names.
44    ///
45    /// Hasura builds a schema per role because a permission is a statement
46    /// about what exists, not about what is allowed: a role with no `select`
47    /// on a table has no field to name, and naming one is a validation failure
48    /// rather than a denial. Each is built from a schema cache already reduced
49    /// to what that role may see.
50    ///
51    /// Built at startup rather than on first use, so a permission document
52    /// that cannot be built fails while someone is watching rather than on the
53    /// first request from whichever role it broke.
54    ///
55    /// Keyed by the role and by whether the caller asked for what only a
56    /// backend may name -- which is a second schema rather than a check,
57    /// because `backend_only` decides whether a field exists.
58    pub role_schemas: HashMap<(String, bool), Schema>,
59    /// What a role the document never names is answered from.
60    ///
61    /// Not a refusal. To Hasura an unrecognised role is a role with nothing
62    /// granted, so it gets a schema with nothing in it -- and a request is
63    /// answered `field 'user' not found in type: 'query_root'`, or `no
64    /// mutations exist` where there is no mutation root at all, rather than
65    /// with a complaint about the role. `address_permission_error` sends
66    /// `X-Hasura-Role: merchant`, which no permission mentions, and expects
67    /// exactly that.
68    ///
69    /// One schema for every such role rather than one each: they are all the
70    /// same schema, since what they were granted is the same nothing.
71    pub ungranted_schema: Option<Schema>,
72    /// Schema configuration
73    pub config: SchemaConfig,
74    /// Subscription fields
75    pub subscription_fields: Vec<SubField>,
76    /// Notification broker for subscriptions
77    pub broker: Arc<RwLock<Option<NotifyBroker>>>,
78}
79
80/// async-graphql's dynamic resolver requires every union value to carry a
81/// concrete type, including `null`. A typed null is then treated as an object,
82/// though, so its non-null fields fail. Convert only the library's error for a
83/// bare null `_Entity` list member back into the nullable member the schema
84/// declares.
85struct NullableFederationEntity;
86
87impl ExtensionFactory for NullableFederationEntity {
88    fn create(&self) -> Arc<dyn Extension> {
89        Arc::new(Self)
90    }
91}
92
93#[async_graphql::async_trait::async_trait]
94impl Extension for NullableFederationEntity {
95    async fn resolve(
96        &self,
97        ctx: &ExtensionContext<'_>,
98        info: ResolveInfo<'_>,
99        next: NextResolve<'_>,
100    ) -> ServerResult<Option<Value>> {
101        // IMPORTANT: async-graphql 7.0.17 invokes this hook for every field and
102        // every list member, not only `_entities`. That leaves one boxed
103        // extension future per call, so do not copy this pattern for other
104        // field-specific behaviour. Its dynamic API cannot represent a bare
105        // nullable union member: an untyped null is rejected and a typed null
106        // is resolved as an object. Until that changes upstream, keep the
107        // successful path to the one required `next.run`; in particular,
108        // defer all string comparisons until the union error has occurred.
109        let parent_type = info.parent_type;
110        let return_type = info.return_type;
111        match next.run(ctx, info).await {
112            Err(error)
113                if error.message
114                    == "internal: invalid value for union \"_Entity\", expected \"FieldValue::WithType\""
115                    && parent_type == "[_Entity]"
116                    && return_type == "_Entity" =>
117            {
118                Ok(Some(Value::Null))
119            }
120            result => result,
121        }
122    }
123}
124
125/// Build one schema for each role the permission document names.
126///
127/// Each is built from a cache reduced to what that role may see, so nothing in
128/// the builders needs to know a permission exists -- a table this role cannot
129/// read is one the code generating root fields cannot see either. See
130/// [`crate::role`] for why the filtering is done there rather than threaded
131/// through.
132///
133/// Empty when the document carries no permissions, which is what leaves an
134/// unconfigured server building exactly the one schema it always did.
135fn build_role_schemas(
136    schema_cache: &Arc<SchemaCache>,
137    config: &SchemaConfig,
138) -> Result<HashMap<(String, bool), Schema>, GraphQLError> {
139    if !config.names.has_permissions() {
140        return Ok(HashMap::new());
141    }
142
143    let mut schemas = HashMap::new();
144    for role in config.names.roles() {
145        // An administrator is not a role with permissions; it is the caller
146        // the permissions do not apply to. Building it a restricted schema
147        // would take the bypass away.
148        if role == postrust_auth::hasura::ADMIN_ROLE {
149            continue;
150        }
151
152        // A second schema only where the role has something a backend alone
153        // may name. Otherwise the one schema answers both, since there is
154        // nothing for the flag to hide.
155        let variants: &[bool] = match crate::role::has_backend_only(&config.names, role) {
156            true => &[false, true],
157            false => &[false],
158        };
159
160        for &backend in variants {
161            let view = Arc::new(crate::role::cache_for_role(
162                schema_cache,
163                &config.names,
164                role,
165                backend,
166            ));
167            let mut role_config = config.clone();
168            role_config.role = Some(role.to_string());
169
170            let generated = build_schema(&view, &role_config);
171            // Subscriptions are left to the unrestricted schema for now: a live
172            // query is one this server answers from notifications rather than from
173            // the query root, so restricting it is a different piece of work from
174            // restricting a read.
175            let built = build_dynamic_schema(
176                &generated,
177                &view,
178                None,
179                role_config.max_rows,
180                Arc::new(role_config.names.clone()),
181                role_config.subscription_refresh(),
182                Some(role),
183            );
184
185            match built {
186                Ok(schema) => {
187                    schemas.insert((role.to_string(), backend), schema);
188                }
189                // One role's permissions being unbuildable is not a reason for
190                // every other role to lose its API, which is what returning here
191                // would mean -- the caller serves no GraphQL at all on an error.
192                // The role is left without a schema instead, which the request
193                // path already reads as a refusal: it fails closed, loudly, and
194                // alone.
195                Err(e) => tracing::error!(
196                    "GraphQL schema for role \"{}\" could not be built, \
197                 so that role is refused: {}",
198                    role,
199                    e
200                ),
201            }
202        }
203    }
204
205    tracing::info!("GraphQL schemas built for {} roles", schemas.len());
206    Ok(schemas)
207}
208
209/// The schema a role the document never names is answered from.
210///
211/// Built the same way every other role's is, from a cache reduced by a role
212/// nothing was granted to -- which leaves no tables, so it is the same schema
213/// whatever the unrecognised role is called. `None` where there is no
214/// permission document, since then no role is unrecognised.
215fn build_ungranted_schema(
216    schema_cache: &Arc<SchemaCache>,
217    config: &SchemaConfig,
218) -> Option<Schema> {
219    if !config.names.has_permissions() {
220        return None;
221    }
222    // A name no permission can carry: `roles()` reads them out of the
223    // document, and this one is not a role anybody wrote down.
224    const NOBODY: &str = "\u{0}ungranted";
225    let view = Arc::new(crate::role::cache_for_role(
226        schema_cache,
227        &config.names,
228        NOBODY,
229        false,
230    ));
231    let mut role_config = config.clone();
232    role_config.role = Some(NOBODY.to_string());
233    let generated = build_schema(&view, &role_config);
234    build_dynamic_schema(
235        &generated,
236        &view,
237        None,
238        role_config.max_rows,
239        Arc::new(role_config.names.clone()),
240        role_config.subscription_refresh(),
241        Some(NOBODY),
242    )
243    .map_err(|e| {
244        tracing::error!(
245            "the schema for a role with nothing granted could not be built,              so such a role is refused instead: {}",
246            e
247        )
248    })
249    .ok()
250}
251
252impl GraphQLState {
253    /// Create new GraphQL state from schema cache.
254    pub fn new(
255        pool: PgPool,
256        schema_cache: Arc<SchemaCache>,
257        config: SchemaConfig,
258    ) -> Result<Self, GraphQLError> {
259        let generated_schema = build_schema(&schema_cache, &config);
260        let subscription_fields = if config.enable_subscriptions {
261            generate_subscription_fields(&schema_cache, &generated_schema)
262        } else {
263            Vec::new()
264        };
265        let schema = build_dynamic_schema(
266            &generated_schema,
267            &schema_cache,
268            if config.enable_subscriptions {
269                Some(subscription_fields.as_slice())
270            } else {
271                None
272            },
273            config.max_rows,
274            Arc::new(config.names.clone()),
275            config.subscription_refresh(),
276            None,
277        )?;
278
279        let role_schemas = build_role_schemas(&schema_cache, &config)?;
280        let ungranted_schema = build_ungranted_schema(&schema_cache, &config);
281
282        Ok(Self {
283            pool: pool.clone(),
284            schema_cache,
285            generated_schema,
286            schema,
287            role_schemas,
288            ungranted_schema,
289            config,
290            subscription_fields,
291            broker: Arc::new(RwLock::new(None)),
292        })
293    }
294
295    /// Which schema answers for a caller speaking as this role.
296    ///
297    /// A role the document does not name is answered from the schema with
298    /// nothing in it, which is Hasura's reading: an unrecognised role is a
299    /// role that was granted nothing, not an error. Three things are
300    /// deliberately not that: a server with no permission document answers
301    /// everyone from the unrestricted schema, an administrator is answered
302    /// from it too -- permissions are rules for everyone else -- and so is a
303    /// request on a server where the layer is off.
304    ///
305    /// `None` only where the empty schema itself could not be built, which the
306    /// caller still turns into a refusal.
307    pub fn schema_for(&self, role: Option<&str>, backend: bool) -> Option<&Schema> {
308        if !self.config.names.has_permissions() {
309            return Some(&self.schema);
310        }
311        match role {
312            None | Some(postrust_auth::hasura::ADMIN_ROLE) => Some(&self.schema),
313            Some(role) => self
314                .role_schemas
315                .get(&(role.to_string(), backend))
316                // A role with nothing backend-only has one schema, under
317                // `false`, and it answers a backend caller too.
318                .or_else(|| self.role_schemas.get(&(role.to_string(), false)))
319                .or(self.ungranted_schema.as_ref()),
320        }
321    }
322
323    /// Rebuild the schema (e.g., after schema cache refresh).
324    #[allow(clippy::needless_update)]
325    pub fn rebuild(&mut self) -> Result<(), GraphQLError> {
326        self.role_schemas = build_role_schemas(&self.schema_cache, &self.config)?;
327        self.ungranted_schema = build_ungranted_schema(&self.schema_cache, &self.config);
328        self.generated_schema = build_schema(&self.schema_cache, &self.config);
329        self.subscription_fields = if self.config.enable_subscriptions {
330            generate_subscription_fields(&self.schema_cache, &self.generated_schema)
331        } else {
332            Vec::new()
333        };
334        self.schema = build_dynamic_schema(
335            &self.generated_schema,
336            &self.schema_cache,
337            if self.config.enable_subscriptions {
338                Some(self.subscription_fields.as_slice())
339            } else {
340                None
341            },
342            self.config.max_rows,
343            Arc::new(self.config.names.clone()),
344            self.config.subscription_refresh(),
345            None,
346        )?;
347        Ok(())
348    }
349
350    /// Initialize the subscription broker.
351    ///
352    /// This should be called after creating the state to enable subscriptions.
353    pub async fn init_subscriptions(&self) -> Result<(), crate::subscription::BrokerError> {
354        if !self.config.enable_subscriptions {
355            return Ok(());
356        }
357
358        let broker = NotifyBroker::new(self.pool.clone());
359
360        // Collect all channels to listen on
361        let channels: Vec<String> = self
362            .subscription_fields
363            .iter()
364            .map(|f| f.channel_name())
365            .collect();
366
367        if !channels.is_empty() {
368            broker.start(channels).await?;
369            info!(
370                "Subscription broker started with {} channels",
371                self.subscription_fields.len()
372            );
373        }
374
375        // Store the broker
376        let mut broker_guard = self.broker.write().await;
377        *broker_guard = Some(broker);
378
379        Ok(())
380    }
381
382    /// Stop the subscription broker.
383    pub async fn stop_subscriptions(&self) {
384        let broker_guard = self.broker.read().await;
385        if let Some(broker) = broker_guard.as_ref() {
386            broker.stop().await;
387        }
388    }
389
390    /// Serve subscriptions on this state from `previous`'s broker, listening
391    /// on whatever channels this schema adds.
392    ///
393    /// For a schema rebuilt under a running server. The broker carries over
394    /// rather than being replaced, so a subscription opened on the previous
395    /// schema keeps receiving notifications. With no broker yet -- start-up
396    /// could not connect one, or there was no previous state -- one is
397    /// started as `init_subscriptions` would.
398    pub async fn continue_subscriptions(
399        &mut self,
400        previous: Option<&GraphQLState>,
401    ) -> Result<(), crate::subscription::BrokerError> {
402        if let Some(previous) = previous {
403            self.broker = Arc::clone(&previous.broker);
404        }
405        if !self.config.enable_subscriptions {
406            return Ok(());
407        }
408        if let Some(broker) = self.broker.read().await.as_ref() {
409            let channels = self
410                .subscription_fields
411                .iter()
412                .map(|f| f.channel_name())
413                .collect();
414            return broker.listen(channels).await;
415        }
416        self.init_subscriptions().await
417    }
418
419    /// Get the notification broker.
420    pub async fn get_broker(&self) -> Option<Arc<RwLock<Option<NotifyBroker>>>> {
421        Some(Arc::clone(&self.broker))
422    }
423}
424
425/// Handle a GraphQL request.
426pub async fn graphql_handler(
427    State(state): State<Arc<GraphQLState>>,
428    ctx: GraphQLContext,
429    req: GraphQLRequest,
430) -> GraphQLResponse {
431    let request = req
432        .into_inner()
433        .data(ctx)
434        .data(state.pool.clone())
435        .data(Arc::clone(&state.broker));
436    state.schema.execute(request).await.into()
437}
438
439/// Handle GraphQL WebSocket subscription upgrade.
440///
441/// This should be called with a WebSocket upgrade request to enable
442/// GraphQL subscriptions over WebSocket.
443///
444/// It authenticates nothing: no caller identity, session or JWT claims reach
445/// the operations it runs. `postrust-server` serves its WebSocket routes with
446/// its own handler, which settles the identity from `connection_init` exactly
447/// as an HTTP request's is settled; an embedder needing the same must insert a
448/// [`GraphQLContext`](crate::context::GraphQLContext) per operation itself.
449pub async fn graphql_ws_handler(
450    State(state): State<Arc<GraphQLState>>,
451    protocol: async_graphql_axum::GraphQLProtocol,
452    ws: axum::extract::WebSocketUpgrade,
453) -> impl IntoResponse {
454    let schema = state.schema.clone();
455    let pool = state.pool.clone();
456    let broker = Arc::clone(&state.broker);
457
458    ws.protocols(["graphql-transport-ws", "graphql-ws"])
459        .on_upgrade(move |socket| async move {
460            let mut data = async_graphql::Data::default();
461            data.insert(pool);
462            data.insert(broker);
463
464            async_graphql_axum::GraphQLWebSocket::new(socket, schema, protocol)
465                .with_data(data)
466                .serve()
467                .await
468        })
469}
470
471/// Handle GraphQL playground request.
472pub async fn graphql_playground() -> impl axum::response::IntoResponse {
473    axum::response::Html(async_graphql::http::playground_source(
474        async_graphql::http::GraphQLPlaygroundConfig::new("/graphql")
475            .subscription_endpoint("/graphql/ws"),
476    ))
477}
478
479/// The column a field name refers to.
480///
481/// The name itself, unless the table exposes that column under another one.
482/// Every path from a request to SQL goes through here: a renamed column is a
483/// name in the schema and in nothing else, so the translation has to happen at
484/// each boundary rather than once.
485fn column_for<'a>(
486    names: &'a crate::names::NameOverrides,
487    schema: &str,
488    table: &str,
489    field: &'a str,
490) -> &'a str {
491    names.column_source(schema, table, field).unwrap_or(field)
492}
493
494/// The same, given the table itself.
495fn table_column_for<'a>(
496    names: &'a crate::names::NameOverrides,
497    table: &postrust_core::schema_cache::Table,
498    field: &'a str,
499) -> &'a str {
500    column_for(names, &table.schema, &table.name, field)
501}
502
503/// The select list that renames a table's columns to the fields they are
504/// exposed as, or `None` where nothing is renamed.
505///
506/// Applied to a projection over the table rather than to the table itself, so
507/// the row a computed field or a computed relationship is passed stays the
508/// table's own composite -- `cannot cast type record to author` is what
509/// happens when it does not.
510fn rename_projection(
511    table: &postrust_core::schema_cache::Table,
512    alias: &str,
513    names: &crate::names::NameOverrides,
514) -> Option<String> {
515    if !names.renames_columns(&table.schema, &table.name) {
516        return None;
517    }
518    let parts: Vec<String> = table
519        .columns
520        .values()
521        .map(|column| {
522            let exposed = names
523                .column(&table.schema, &table.name, &column.name)
524                .unwrap_or(&column.name);
525            format!(
526                "{}.{} AS {}",
527                postrust_sql::escape_ident(alias),
528                postrust_sql::escape_ident(&column.name),
529                postrust_sql::escape_ident(exposed)
530            )
531        })
532        .collect();
533    match parts.is_empty() {
534        true => None,
535        false => Some(parts.join(", ")),
536    }
537}
538
539/// A table's column types, keyed by the names those columns are exposed under.
540///
541/// [`row_json`] names the shape columns it has to rewrite as GeoJSON, and it
542/// names them in whatever the projection called them. Over a renamed
543/// projection that is the field name, not the column's.
544fn exposed_column_types(
545    table: &postrust_core::schema_cache::Table,
546    names: &crate::names::NameOverrides,
547) -> HashMap<String, String> {
548    table
549        .columns
550        .values()
551        .map(|column| {
552            let exposed = names
553                .column(&table.schema, &table.name, &column.name)
554                .unwrap_or(&column.name);
555            (exposed.to_string(), column.nominal_type.clone())
556        })
557        .collect()
558}
559
560/// The type a value is written as, which keeps the list an array column is.
561///
562/// Not the leaf scalar: `c34_text_array` is a `text[]`, and a client sending
563/// `$textArray: [String]` into it was refused for offering a list where the
564/// input said `String`. Every write is optional, so nothing here is non-null.
565fn write_type_ref(graphql_type: &crate::types::GraphQLType) -> TypeRef {
566    match graphql_type {
567        crate::types::GraphQLType::List(inner) => TypeRef::named_list(leaf_scalar_name(inner)),
568        other => TypeRef::named(leaf_scalar_name(other)),
569    }
570}
571
572/// The scalar at the bottom of a type, past any list wrapping.
573fn leaf_scalar_name(graphql_type: &crate::types::GraphQLType) -> String {
574    match graphql_type {
575        crate::types::GraphQLType::List(inner) => leaf_scalar_name(inner),
576        other => other.to_string(),
577    }
578}
579
580/// Build the dynamic async-graphql schema from our generated schema.
581fn build_dynamic_schema(
582    generated: &GeneratedSchema,
583    _schema_cache: &SchemaCache,
584    subscription_fields: Option<&[SubField]>,
585    max_rows: Option<i64>,
586    names: Arc<crate::names::NameOverrides>,
587    subscription_refresh: std::time::Duration,
588    // Whose schema this is, where it is one role's. What it decides here is
589    // which columns a write may name, which is not the same question as which
590    // columns may be read -- Hasura grants the two separately, and a role that
591    // may set a column it cannot read is ordinary rather than exotic.
592    role: Option<&str>,
593) -> Result<Schema, GraphQLError> {
594    // Create object types for each table
595    let mut object_types: HashMap<String, Object> = HashMap::new();
596
597    // A table with no readable field gets no type at all. That is not an
598    // empty table -- it is one this role may write and not read, and a GraphQL
599    // object type with no fields is not a legal type. Everything that would
600    // have returned it is left out with it.
601    for (type_name, obj) in &generated.object_types {
602        if obj.fields.is_empty() {
603            continue;
604        }
605        let relationships = generated
606            .relationship_fields
607            .get(type_name)
608            .map(|r| r.as_slice())
609            .unwrap_or(&[]);
610        let table_obj = create_object_type(
611            obj,
612            relationships,
613            generated.federation_entities.get(type_name),
614        );
615        object_types.insert(type_name.clone(), table_obj);
616    }
617
618    // Aggregate types: every readable table gets them, because every table has
619    // a count even when it has nothing to sum -- and so does a table a role may
620    // count and not read, which is the whole of what such a role was granted.
621    // Read from the roots that were generated rather than worked out again,
622    // for the reason the write inputs are: two answers to one question drift.
623    let countable: HashSet<&str> = generated
624        .query_fields
625        .iter()
626        .filter(|field| field.aggregates && !field.is_by_pk)
627        .map(|field| field.type_name.as_str())
628        .collect();
629    for (type_name, obj) in &generated.object_types {
630        if obj.fields.is_empty() && !countable.contains(type_name.as_str()) {
631            continue;
632        }
633        for aggregate_type in create_aggregate_types(&obj.local_name, obj) {
634            object_types.insert(aggregate_type.type_name().to_string(), aggregate_type);
635        }
636    }
637
638    // One mutation response per table that has any mutation, and only those:
639    // an unreferenced type is still a type, and a read-only view would
640    // otherwise contribute a response nothing can return.
641    let mutable: HashMap<String, String> = generated
642        .mutation_fields
643        .iter()
644        .filter_map(|f| {
645            // The table's own name. A bulk write already answers with the
646            // response type, and trimming the suffix here is what keeps
647            // `<t>_mutation_response_mutation_response` from being built: the
648            // set used to be carried by `insert_one` and `update_by_pk`
649            // naming the bare type, so a table with only bulk writes -- which
650            // is what a role that cannot read one has -- registered the wrong
651            // name and left the right one missing.
652            let response = f
653                .return_type
654                .trim_matches(|c| c == '[' || c == ']' || c == '!')
655                .strip_suffix("_mutation_response")?;
656            Some((response.to_string(), f.type_name.clone()))
657        })
658        .collect();
659    for (base_name, row_type_name) in mutable {
660        // Whether it has rows to give back. A write to a table this role
661        // cannot read answers with `affected_rows` and nothing else -- there
662        // is no row type for `returning` to be a list of.
663        let returning = generated
664            .object_types
665            .get(&row_type_name)
666            .is_some_and(|object| !object.fields.is_empty());
667        object_types.insert(
668            mutation_response_type_name(&base_name),
669            create_mutation_response_type(&base_name, &row_type_name, returning),
670        );
671    }
672
673    // Non-federated Hasura-compatible schemas use `query_root`, not `Query`.
674    // Federation SDL from async-graphql does not include an operation-root
675    // schema definition, so Apollo composition treats only the conventional
676    // root names as root types.
677    let query_root = if generated.enable_federation {
678        "Query"
679    } else {
680        "query_root"
681    };
682    let mutation_root = if generated.enable_federation {
683        "Mutation"
684    } else {
685        "mutation_root"
686    };
687    let subscription_root = if generated.enable_federation {
688        "Subscription"
689    } else {
690        "subscription_root"
691    };
692
693    // Create query type. Resolvers need the relationship map to embed related
694    // rows, so it is shared into each closure.
695    let relationships = Arc::new(generated.relationship_fields.clone());
696    // (schema, table) -> GraphQL type name. A nested insert is handed a table
697    // by a relationship and has to find that table's relationships in turn,
698    // and the map they live in is keyed by type name -- which is not always
699    // the table's name, since a second schema prefixes it and a name may have
700    // been given.
701    let type_names: Arc<HashMap<(String, String), String>> = Arc::new(
702        generated
703            .object_types
704            .iter()
705            .map(|(type_name, object)| {
706                (
707                    (object.table.schema.clone(), object.table.name.clone()),
708                    type_name.clone(),
709                )
710            })
711            .collect(),
712    );
713    let query = add_function_fields(
714        create_query_type(
715            generated,
716            query_root,
717            max_rows,
718            Arc::clone(&relationships),
719            Arc::clone(&names),
720        ),
721        generated,
722        false,
723        max_rows,
724        Arc::clone(&relationships),
725        Arc::clone(&names),
726    );
727
728    // Create mutation type
729    let mutation = if !generated.mutation_fields.is_empty()
730        || generated.function_fields.iter().any(|f| f.volatile)
731    {
732        Some(add_function_fields(
733            create_mutation_type(
734                generated,
735                mutation_root,
736                Arc::clone(&relationships),
737                Arc::clone(&type_names),
738                Arc::clone(&names),
739                max_rows,
740                role,
741            ),
742            generated,
743            true,
744            max_rows,
745            Arc::clone(&relationships),
746            Arc::clone(&names),
747        ))
748    } else {
749        None
750    };
751
752    // Create subscription type if enabled
753    let subscription = subscription_fields.map(|_| {
754        create_subscription_type(
755            generated,
756            subscription_root,
757            max_rows,
758            Arc::clone(&relationships),
759            Arc::clone(&names),
760            subscription_refresh,
761        )
762    });
763
764    // Build schema
765    let mut builder = Schema::build(
766        query_root,
767        mutation.as_ref().map(|_| mutation_root),
768        subscription.as_ref().map(|_| subscription_root),
769    );
770    if generated.enable_federation {
771        builder = builder
772            .enable_federation()
773            .extension(NullableFederationEntity);
774        let entity_state = Arc::new(EntityResolverState::new(
775            generated,
776            Arc::clone(&relationships),
777            Arc::clone(&names),
778            max_rows,
779        ));
780        builder = builder.entity_resolver(move |ctx| {
781            let state = Arc::clone(&entity_state);
782            FieldFuture::new(async move { resolve_entities(&ctx, state.as_ref()).await })
783        });
784    }
785
786    // Register all object types
787    for (_, obj) in object_types {
788        builder = builder.register(obj);
789    }
790
791    // Register query type
792    builder = builder.register(query);
793
794    // Register mutation type if present
795    if let Some(mutation) = mutation {
796        builder = builder.register(mutation);
797    }
798
799    // Register subscription type if present
800    if let Some(subscription) = subscription {
801        builder = builder.register(subscription);
802    }
803
804    // Register scalar types
805    // Every scalar the generated types actually name, rather than a fixed
806    // list. A `geometry` column, a `raster` column and a database enum each
807    // become a scalar under their own PostgreSQL name, because that is the
808    // name a client's query declares its variables with -- and a scalar the
809    // schema mentions but never registers makes the whole schema unbuildable.
810    let (bool_exp_inputs, bool_exp_scalars) = crate::input::bool_exp::build_inputs(
811        &generated.object_types,
812        &generated.relationship_fields,
813    );
814    for input in bool_exp_inputs {
815        builder = builder.register(input);
816    }
817
818    let mut scalar_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
819    for object in generated.object_types.values() {
820        // Both halves. A column a role may set without seeing is named by the
821        // insert input and by nothing a read produces, so collecting only the
822        // readable fields leaves its scalar unregistered -- and a scalar the
823        // schema mentions but never registers makes the whole schema
824        // unbuildable, which costs the role every field it has.
825        for field in object.fields.iter().chain(&object.writable_fields) {
826            scalar_names.insert(leaf_scalar_name(&field.graphql_type));
827        }
828    }
829    // Used as an argument type in its own right, whether or not any column is
830    // one: `objects`, `_set` and the mutation inputs are still JSON.
831    scalar_names.insert("JSON".to_string());
832    // A function's arguments name scalars of their own: a table may have no
833    // geometry column while a function takes one.
834    for function in &generated.function_fields {
835        for (_, pg_type, _) in &function.arguments {
836            scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
837        }
838    }
839    // The same for the functions behind computed fields, whose arguments are
840    // written where the field is asked for rather than at a root.
841    for object in generated.object_types.values() {
842        for field in &object.fields {
843            for (_, pg_type, _) in &field.arguments {
844                scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
845            }
846        }
847    }
848    for relationships in generated.relationship_fields.values() {
849        for relationship in relationships {
850            for (_, pg_type, _) in &relationship.arguments {
851                scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
852            }
853        }
854    }
855
856    // Every scalar the boolean expressions name, which is more than the
857    // scalars the columns are: a cast from a geometry names `geography`, and a
858    // raster comparison names `geometry`. Taken from what was generated rather
859    // than listed here, because a list here is a second place to keep in step
860    // and it fell out of step twice.
861    scalar_names.extend(bool_exp_scalars);
862
863    for name in scalar_names {
864        if matches!(name.as_str(), "Int" | "Float" | "String" | "Boolean" | "ID") {
865            continue;
866        }
867        // An enum table's type is an enum, not a scalar. It reaches this list
868        // because a column typed as one names it, and registering both would
869        // define the same type twice.
870        if generated.enum_types.contains_key(&name) {
871            continue;
872        }
873        builder = builder
874            .register(Scalar::new(&name).description(format!("The PostgreSQL `{}` type.", name)));
875    }
876
877    // Register the boolean expression inputs -- one per table, plus one
878    // comparison input per scalar the tables use.
879
880    // A key as one object, for `update_x_by_pk(pk_columns: {...})`. Only the
881    // by-key update spells its key this way; the by-key query and delete take
882    // the columns as arguments, and both spellings are what a generated client
883    // already sends.
884    let mut key_inputs: HashMap<String, InputObject> = HashMap::new();
885    for field in &generated.mutation_fields {
886        if field.mutation_type != MutationType::UpdateByPk || field.pk_columns.is_empty() {
887            continue;
888        }
889        let base_name = field.local_type_name.as_str();
890        let type_name = format!("{}_pk_columns_input", base_name);
891        let mut input = InputObject::new(&type_name)
892            .description(format!("The primary key of one {} row.", base_name));
893        for (column, pg_type) in &field.pk_columns {
894            input = input.field(InputValue::new(
895                names
896                    .column(&field.schema_name, &field.table_name, column)
897                    .unwrap_or(column),
898                TypeRef::named_nn(exposed_pk_argument_type(
899                    names.as_ref(),
900                    &field.schema_name,
901                    &field.table_name,
902                    column,
903                    pg_type,
904                )),
905            ));
906        }
907        key_inputs.insert(type_name, input);
908    }
909    for (_, input) in key_inputs {
910        builder = builder.register(input);
911    }
912
913    // What a mutation writes. `objects` and `_set` were `JSON`, which accepts
914    // anything and lets a client generate nothing: no completion, no codegen,
915    // and a misspelled column reaching the database instead of being caught
916    // before the request was sent. The same argument that made `where` a real
917    // type applies unchanged.
918    // Which types have an `_insert_input` at all, which is what a nested write
919    // names. Collected first because a relationship may point at a table this
920    // loop has not reached yet -- and because the answer is now a permission
921    // question: a role granted `insert` on `article` and only `select` on
922    // `author` gets an `article_insert_input`, and writing `author` inside it
923    // would name a type nobody registered. async-graphql refuses to build a
924    // schema with a dangling reference, so the whole role would have no schema
925    // over one field it was never allowed to use.
926    // Which types actually got each write input, recorded as they are built
927    // rather than predicted from the object type's fields. The two answers
928    // used to be the same and are not any more: an input is built from the
929    // write permission's columns, so a table whose readable columns include a
930    // number may still have no `_inc_input` -- and an argument naming a type
931    // nobody registered is a schema async-graphql refuses to build, which
932    // costs the role every field it has rather than the one it cannot use.
933    let insertable_types: HashSet<&str> = generated
934        .object_types
935        .iter()
936        .filter(|(_, object)| crate::input::mutation::is_insertable(&object.table))
937        .map(|(type_name, _)| type_name.as_str())
938        .collect();
939
940    for (type_name, object) in &generated.object_types {
941        let local_type_name = object.local_name.as_str();
942        let table = &object.table;
943        // Which columns a write may name, which is the write permission's
944        // answer and not the read permission's. A role granted `columns:
945        // ["age"]` on insert may not name `is_user` even where it can read it,
946        // and the corpus tests exactly that: `field 'is_user' not found in
947        // type: 'resident_insert_input'`.
948        // Only real columns are written. A computed field is a function of the
949        // row and a relationship is handled separately below.
950        let writable_named = |allowed: Option<&crate::names::ColumnSet>| {
951            object
952                .writable_fields
953                .iter()
954                .filter(|field| {
955                    let column = table_column_for(&names, table, &field.name);
956                    table.get_column(column).is_some()
957                        && allowed.is_none_or(|set| set.allows(column))
958                })
959                .collect::<Vec<&crate::schema::object::GraphQLField>>()
960        };
961        let writable = writable_named(None);
962        let insert_fields = write_fields(object, &names, role, crate::role::Verb::Insert);
963        let update_fields = write_fields(object, &names, role, crate::role::Verb::Update);
964        if writable.is_empty() {
965            continue;
966        }
967
968        let relationships = generated
969            .relationship_fields
970            .get(type_name)
971            .map(|r| r.as_slice())
972            .unwrap_or(&[]);
973        let conflict_type = match table.unique_constraints.is_empty() {
974            true => None,
975            false => Some(format!("{}_on_conflict", local_type_name)),
976        };
977
978        if crate::input::mutation::is_insertable(table) {
979            let mut insert = InputObject::new(format!("{}_insert_input", local_type_name))
980                .description(format!("The columns of a new {} row.", local_type_name));
981            let mut taken: HashSet<&str> = HashSet::new();
982            for field in &insert_fields {
983                // Every column optional: which ones the database insists on is
984                // the database's answer, and a column that is NOT NULL with a
985                // default does not have to be given.
986                taken.insert(field.name.as_str());
987                insert = insert.field(InputValue::new(
988                    &field.name,
989                    write_type_ref(&field.graphql_type),
990                ));
991            }
992            // A nested write: the rows to insert beside this one. Only where
993            // the far side is something this schema can write -- see
994            // `insertable_types`.
995            for relationship in relationships {
996                if !insertable_types.contains(relationship.target_type.as_str()) {
997                    continue;
998                }
999                if !taken.insert(relationship.name.as_str()) {
1000                    continue;
1001                }
1002                let suffix = match relationship.is_list {
1003                    true => "arr_rel_insert_input",
1004                    false => "obj_rel_insert_input",
1005                };
1006                insert = insert.field(InputValue::new(
1007                    &relationship.name,
1008                    TypeRef::named(format!("{}_{}", relationship.target_local_name, suffix)),
1009                ));
1010            }
1011            builder = builder.register(insert);
1012
1013            // How this table is written as somebody else's nested row. Both
1014            // shapes carry an `on_conflict`, which is what makes a nested
1015            // upsert expressible.
1016            let data_type = format!("{}_insert_input", local_type_name);
1017            let mut object_rel =
1018                InputObject::new(format!("{}_obj_rel_insert_input", local_type_name))
1019                    .description(format!(
1020                        "One {} row written beside its parent.",
1021                        local_type_name
1022                    ))
1023                    .field(InputValue::new("data", TypeRef::named_nn(&data_type)));
1024            let mut array_rel =
1025                InputObject::new(format!("{}_arr_rel_insert_input", local_type_name))
1026                    .description(format!(
1027                        "{} rows written beside their parent.",
1028                        local_type_name
1029                    ))
1030                    .field(InputValue::new(
1031                        "data",
1032                        TypeRef::named_nn_list_nn(&data_type),
1033                    ));
1034            if let Some(conflict) = &conflict_type {
1035                object_rel =
1036                    object_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1037                array_rel =
1038                    array_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1039            }
1040            builder = builder.register(object_rel).register(array_rel);
1041        }
1042
1043        if crate::input::mutation::is_updatable(table) {
1044            let mut set = InputObject::new(format!("{}_set_input", local_type_name))
1045                .description(format!("Columns of {} to replace.", local_type_name));
1046            let mut numeric = InputObject::new(format!("{}_inc_input", local_type_name))
1047                .description(format!("Columns of {} to add to.", local_type_name));
1048            let mut any_numeric = false;
1049            let mut any_jsonb = false;
1050            for field in &update_fields {
1051                if matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1052                    any_jsonb = true;
1053                }
1054                set = set.field(InputValue::new(
1055                    &field.name,
1056                    write_type_ref(&field.graphql_type),
1057                ));
1058                // Only a number can be added to, and a list of them is not a
1059                // number: `_inc` takes the scalar or nothing.
1060                if crate::schema::aggregate::is_incrementable(&field.graphql_type) {
1061                    any_numeric = true;
1062                    numeric = numeric.field(InputValue::new(
1063                        &field.name,
1064                        TypeRef::named(leaf_scalar_name(&field.graphql_type)),
1065                    ));
1066                }
1067            }
1068            builder = builder.register(set);
1069            // A table with nothing to add to gets no type for adding to it.
1070            if any_numeric {
1071                builder = builder.register(numeric);
1072            }
1073
1074            // One entry of `update_x_many`: a filter and the values to write
1075            // where it matches. The whole list runs in one transaction, in the
1076            // order it was given, which is what makes it different from
1077            // sending the updates one at a time.
1078            let mut updates = InputObject::new(format!("{}_updates", local_type_name))
1079                .description(format!(
1080                    "One update to {}: which rows, and what to write.",
1081                    local_type_name
1082                ))
1083                .field(InputValue::new(
1084                    "where",
1085                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1086                ))
1087                .field(InputValue::new(
1088                    "_set",
1089                    TypeRef::named(format!("{}_set_input", local_type_name)),
1090                ));
1091            if any_numeric {
1092                updates = updates.field(InputValue::new(
1093                    "_inc",
1094                    TypeRef::named(format!("{}_inc_input", local_type_name)),
1095                ));
1096            }
1097            // What a document column may be told to do, one input per
1098            // operator: each takes a value of a different shape -- a
1099            // document, a key, an index, a path -- and only the columns that
1100            // hold documents may be told any of it. A table with none is not
1101            // given the operators at all, which is what says in the schema
1102            // that `_append` is not a thing to write there.
1103            for (operator, _) in JSONB_OPERATORS {
1104                if !any_jsonb {
1105                    break;
1106                }
1107                updates = updates.field(InputValue::new(
1108                    *operator,
1109                    TypeRef::named(jsonb_operator_input(local_type_name, operator)),
1110                ));
1111            }
1112            builder = builder.register(updates);
1113            if any_jsonb {
1114                for (operator, item) in JSONB_OPERATORS {
1115                    let mut input =
1116                        InputObject::new(jsonb_operator_input(local_type_name, operator))
1117                            .description(format!(
1118                                "Columns of {} to apply `{}` to.",
1119                                local_type_name, operator
1120                            ));
1121                    for field in &update_fields {
1122                        if !matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1123                            continue;
1124                        }
1125                        input = input.field(InputValue::new(&field.name, item()));
1126                    }
1127                    builder = builder.register(input);
1128                }
1129            }
1130        }
1131    }
1132
1133    // A function's own arguments, under a name of their own so they cannot
1134    // collide with `where` or `limit`.
1135    for function in &generated.function_fields {
1136        if function.arguments.is_empty() {
1137            continue;
1138        }
1139        let mut args = InputObject::new(format!("{}_args", function.name))
1140            .description(format!("Arguments to {}.", function.name));
1141        for (name, pg_type, _) in &function.arguments {
1142            let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1143            // Nullable, whether or not the parameter has a default. Every
1144            // PostgreSQL argument accepts a null, and a client passing one
1145            // through a variable declares it as the nullable type -- `query
1146            // ($point: json)` used where `json!` was expected is a query the
1147            // spec's variable rule refuses. Whether a defaulted argument may
1148            // be left out entirely is enforced where the call is written,
1149            // which is the only place that can tell.
1150            args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1151        }
1152        builder = builder.register(args);
1153    }
1154
1155    // The same for a computed relationship's function, which takes its
1156    // arguments where it is embedded rather than at the root. Named after the
1157    // type and the field, since two tables may reach the same function and one
1158    // table may reach two.
1159    for (type_name, relationships) in &generated.relationship_fields {
1160        let local_type_name = generated
1161            .object_types
1162            .get(type_name)
1163            .map(|object| object.local_name.as_str())
1164            .unwrap_or(type_name);
1165        for relationship in relationships {
1166            if relationship.arguments.is_empty() {
1167                continue;
1168            }
1169            let mut args =
1170                InputObject::new(computed_args_type_name(local_type_name, &relationship.name))
1171                    .description(format!(
1172                        "Arguments to {}, beside the row it is asked of.",
1173                        relationship.name
1174                    ));
1175            for (name, pg_type, _) in &relationship.arguments {
1176                let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1177                // Nullable, for the reason the function arguments above are.
1178                args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1179            }
1180            builder = builder.register(args);
1181        }
1182    }
1183
1184    // And for a computed *column*'s function, which likewise takes its
1185    // arguments where the field is asked for. `locations { distance(args: {
1186    // from: ... }) }` -- the field is a function of the row and of what the
1187    // caller wants measured against it.
1188    for object in generated.object_types.values() {
1189        let local_type_name = object.local_name.as_str();
1190        for field in &object.fields {
1191            if field.arguments.is_empty() {
1192                continue;
1193            }
1194            let mut args = InputObject::new(computed_args_type_name(local_type_name, &field.name))
1195                .description(format!(
1196                    "Arguments to {}, beside the row it is asked of.",
1197                    field.name
1198                ));
1199            for (name, pg_type, _) in &field.arguments {
1200                let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1201                // Nullable, for the reason the function arguments above are.
1202                args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1203            }
1204            builder = builder.register(args);
1205        }
1206    }
1207
1208    // What a predicate over a related set may ask. Registered only for the
1209    // tables something points at with a to-many relationship, which is the
1210    // only place the field naming them exists.
1211    {
1212        use crate::input::bool_exp as be;
1213        let mut targets: BTreeMap<&str, &str> = BTreeMap::new();
1214        for relationships in generated.relationship_fields.values() {
1215            for relationship in relationships {
1216                if relationship.is_list {
1217                    targets.insert(
1218                        relationship.target_local_name.as_str(),
1219                        relationship.target_type.as_str(),
1220                    );
1221                }
1222            }
1223        }
1224        for (target, target_type) in targets {
1225            let Some(object) = generated.object_types.get(target_type) else {
1226                continue;
1227            };
1228            // A boolean column is what `bool_and` and `bool_or` fold, and a
1229            // GraphQL enum may not be empty -- so a table with none gets
1230            // neither the aggregates nor the enum naming their columns.
1231            let booleans: Vec<&str> = object
1232                .fields
1233                .iter()
1234                .filter(|field| {
1235                    object.table.get_column(&field.name).is_some()
1236                        && matches!(field.graphql_type, crate::types::GraphQLType::Boolean)
1237                })
1238                .map(|field| field.name.as_str())
1239                .collect();
1240
1241            let mut over = InputObject::new(be::aggregate_bool_exp_type_name(target)).description(
1242                format!("A question about the whole set of related {} rows.", target),
1243            );
1244            for function in be::AGGREGATE_PREDICATES {
1245                if function != "count" && booleans.is_empty() {
1246                    continue;
1247                }
1248                over = over.field(InputValue::new(
1249                    function,
1250                    TypeRef::named(be::aggregate_bool_exp_function(target, function)),
1251                ));
1252
1253                // `count` may be told which columns to count, as the field
1254                // itself may; `bool_and` folds exactly one.
1255                let (arguments, predicate) = match function {
1256                    "count" => (
1257                        TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1258                            target,
1259                        )),
1260                        be::comparison_type_name("Int"),
1261                    ),
1262                    _ => (
1263                        TypeRef::named_nn(be::aggregate_bool_exp_columns(target, function)),
1264                        be::comparison_type_name("Boolean"),
1265                    ),
1266                };
1267                let mut input = InputObject::new(be::aggregate_bool_exp_function(target, function))
1268                    .description(format!(
1269                        "`{}` over the related {} rows, and what it has to be.",
1270                        function, target
1271                    ))
1272                    .field(InputValue::new("arguments", arguments))
1273                    .field(InputValue::new("distinct", TypeRef::named("Boolean")))
1274                    .field(InputValue::new(
1275                        "filter",
1276                        TypeRef::named(be::bool_exp_type_name(target)),
1277                    ))
1278                    .field(InputValue::new("predicate", TypeRef::named_nn(predicate)));
1279                if function == "count" {
1280                    input = input.description(format!(
1281                        "How many related {} rows there have to be.",
1282                        target
1283                    ));
1284                }
1285                builder = builder.register(input);
1286
1287                if function != "count" {
1288                    let mut columns = Enum::new(be::aggregate_bool_exp_columns(target, function))
1289                        .description(format!("A boolean column of {}.", target));
1290                    for name in &booleans {
1291                        columns = columns.item(EnumItem::new(*name));
1292                    }
1293                    builder = builder.register(columns);
1294                }
1295            }
1296            builder = builder.register(over);
1297        }
1298    }
1299
1300    // Upserts. A table with no unique constraint has no conflict to resolve,
1301    // and a GraphQL enum may not be empty, so it gets none of these types
1302    // rather than an unusable set of them.
1303    for object in generated.object_types.values() {
1304        let local_type_name = object.local_name.as_str();
1305        // What an upsert may write, which is the update permission's answer
1306        // and not the read permission's: `update_columns` names columns to
1307        // overwrite, so a role that may set a column without seeing it may
1308        // name it here. Where no update permission narrows them these are the
1309        // table's own columns, which is what the enum listed before.
1310        let updatable_columns = write_fields(object, &names, role, crate::role::Verb::Update);
1311        if object.table.unique_constraints.is_empty() {
1312            continue;
1313        }
1314
1315        let mut constraints =
1316            Enum::new(format!("{}_constraint", local_type_name)).description(format!(
1317                "A uniqueness of {} that an insert may conflict with.",
1318                local_type_name
1319            ));
1320        for (name, columns) in &object.table.unique_constraints {
1321            constraints = constraints
1322                .item(EnumItem::new(name).description(format!("unique ({})", columns.join(", "))));
1323        }
1324
1325        let mut updatable = Enum::new(format!("{}_update_column", local_type_name)).description(
1326            format!("A column of {} an upsert may write.", local_type_name),
1327        );
1328        for field in &updatable_columns {
1329            updatable = updatable.item(EnumItem::new(&field.name));
1330        }
1331        // An update permission granting no column leaves the enum with no
1332        // members, which is not a legal type -- and dropping the enum would
1333        // drop `on_conflict` with it, so an insert that names one would fail
1334        // to build rather than be refused. Hasura's answer is a member that
1335        // names no column: the upsert is still expressible, `update_columns:
1336        // []` still means `DO NOTHING`, and naming the placeholder is refused
1337        // where it is used. `article_on_conflict_restricted_role.yaml` tests
1338        // both halves.
1339        if updatable_columns.is_empty() {
1340            updatable = updatable.item(
1341                EnumItem::new(PLACEHOLDER_COLUMN)
1342                    .description("No column may be written by this upsert."),
1343            );
1344        }
1345
1346        let on_conflict = InputObject::new(format!("{}_on_conflict", local_type_name))
1347            .description(format!(
1348                "What to do when an insert into {} conflicts.",
1349                local_type_name
1350            ))
1351            .field(InputValue::new(
1352                "constraint",
1353                TypeRef::named_nn(format!("{}_constraint", local_type_name)),
1354            ))
1355            // An empty list is `DO NOTHING`, which is how Hasura spells "leave
1356            // the row that is already there alone" -- and is the default, so
1357            // `on_conflict: {constraint: article_pkey}` means exactly that
1358            // rather than being refused for saying nothing about columns.
1359            .field(
1360                InputValue::new(
1361                    "update_columns",
1362                    TypeRef::named_nn_list_nn(format!("{}_update_column", local_type_name)),
1363                )
1364                .default_value(Value::List(Vec::new())),
1365            )
1366            .field(InputValue::new(
1367                "where",
1368                TypeRef::named(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1369            ));
1370
1371        builder = builder
1372            .register(constraints)
1373            .register(updatable)
1374            .register(on_conflict);
1375    }
1376
1377    // The enums generated from enum tables.
1378    for (type_name, members) in &generated.enum_types {
1379        let mut generated_enum =
1380            Enum::new(type_name).description(format!("The values {} allows.", type_name));
1381        for (value, comment) in members {
1382            let item = EnumItem::new(value);
1383            generated_enum = generated_enum.item(match comment {
1384                Some(description) => item.description(description),
1385                None => item,
1386            });
1387        }
1388        builder = builder.register(generated_enum);
1389    }
1390
1391    // Ordering: one input per table, one column enum per table, and the single
1392    // direction enum they all share.
1393    let (order_inputs, order_enums) = crate::input::order_by::build_inputs(
1394        &generated.object_types,
1395        &generated.relationship_fields,
1396    );
1397    for input in order_inputs {
1398        builder = builder.register(input);
1399    }
1400    for enum_type in order_enums {
1401        builder = builder.register(enum_type);
1402    }
1403
1404    builder
1405        .finish()
1406        .map_err(|e| GraphQLError::SchemaError(e.to_string()))
1407}
1408
1409/// Create an object type from a TableObjectType.
1410/// The enum member that stands in for an update permission granting no column.
1411///
1412/// Hasura's spelling, and it is load-bearing: `article_update_column` must be
1413/// a legal enum for `article_on_conflict` to exist, and `on_conflict` must
1414/// exist for an insert that carries one to be refused rather than unbuildable.
1415const PLACEHOLDER_COLUMN: &str = "_PLACEHOLDER";
1416
1417/// The name of a table's mutation response type.
1418pub fn mutation_response_type_name(base_name: &str) -> String {
1419    format!("{}_mutation_response", base_name)
1420}
1421
1422/// Build the `<table>_mutation_response` object.
1423///
1424/// Every bulk mutation answers with this rather than with the rows directly.
1425/// `affected_rows` is the count PostgreSQL reports, which is not the length of
1426/// `returning`: a client may ask for no rows back at all and still need to know
1427/// how many were touched, and that is the usual case for a delete.
1428fn create_mutation_response_type(base_name: &str, row_type: &str, returning: bool) -> Object {
1429    let response_name = mutation_response_type_name(base_name);
1430
1431    let response = Object::new(&response_name)
1432        .description(format!("The rows {} changed, and how many.", base_name))
1433        .field(Field::new(
1434            "affected_rows",
1435            TypeRef::named_nn(TypeRef::INT),
1436            |ctx| {
1437                FieldFuture::new(async move {
1438                    if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1439                        if let Some(value) = map.get(&async_graphql::Name::new("affected_rows")) {
1440                            return Ok(Some(FieldValue::value(value.clone())));
1441                        }
1442                    }
1443                    Ok(Some(FieldValue::value(Value::from(0))))
1444                })
1445            },
1446        ));
1447    // A table this role may write and not read has no row type, so there is
1448    // nothing for `returning` to be a list of. Hasura answers such a write
1449    // with the count alone, and the corpus asks for nothing else.
1450    if !returning {
1451        return response;
1452    }
1453    response.field(Field::new(
1454        "returning",
1455        TypeRef::named_nn_list_nn(row_type.to_string()),
1456        |ctx| {
1457            FieldFuture::new(async move {
1458                let rows = match ctx.parent_value.as_value() {
1459                    Some(Value::Object(map)) => {
1460                        match map.get(&async_graphql::Name::new("returning")) {
1461                            Some(Value::List(items)) => items.clone(),
1462                            _ => Vec::new(),
1463                        }
1464                    }
1465                    _ => Vec::new(),
1466                };
1467                Ok(Some(FieldValue::list(
1468                    rows.into_iter().map(FieldValue::value),
1469                )))
1470            })
1471        },
1472    ))
1473}
1474
1475/// Build every aggregate type for one table.
1476///
1477/// Four kinds, and they nest: `<t>_aggregate` holds `aggregate` and `nodes`,
1478/// `<t>_aggregate_fields` holds `count` and one field per function, and each
1479/// function has a type of its own holding the columns it applies to.
1480fn create_aggregate_types(base_name: &str, object: &TableObjectType) -> Vec<Object> {
1481    use crate::schema::aggregate as agg;
1482
1483    let mut types = Vec::new();
1484
1485    // `<t>_aggregate`: the rows, and the numbers about them.
1486    let fields_type = agg::aggregate_fields_type_name(base_name);
1487    // Whether there are rows to hand back beside the numbers. A role granted
1488    // "how many" and not "which" has no row type, so `nodes` has nothing to be
1489    // a list of.
1490    let has_rows = !object.fields.is_empty();
1491    let mut over = Object::new(agg::aggregate_type_name(base_name))
1492        .description(match has_rows {
1493            true => format!("Aggregates over {}, with the rows themselves.", base_name),
1494            false => format!("Aggregates over {}.", base_name),
1495        })
1496        .field(Field::new(
1497            "aggregate",
1498            TypeRef::named(&fields_type),
1499            |ctx| FieldFuture::new(async move { Ok(child_of(&ctx, "aggregate")) }),
1500        ));
1501    if has_rows {
1502        over = over.field(Field::new(
1503            "nodes",
1504            TypeRef::named_nn_list_nn(object.name.clone()),
1505            |ctx| {
1506                FieldFuture::new(async move {
1507                    let rows = match child_value(&ctx, "nodes") {
1508                        Some(Value::List(items)) => items,
1509                        _ => Vec::new(),
1510                    };
1511                    Ok(Some(FieldValue::list(
1512                        rows.into_iter().map(FieldValue::value),
1513                    )))
1514                })
1515            },
1516        ));
1517    }
1518    types.push(over);
1519
1520    // `<t>_aggregate_fields`: count, and one field per function.
1521    let mut count = Field::new("count", TypeRef::named_nn(TypeRef::INT), |ctx| {
1522        // Read under the name it was asked for, not under `count`.
1523        // Two counts of different things sit in one selection --
1524        // `count` beside `distinct_authors: count(columns: [author_id],
1525        // distinct: true)` -- and they are different numbers.
1526        let key = ctx.ctx.field().alias().unwrap_or("count").to_string();
1527        FieldFuture::new(async move {
1528            Ok(Some(FieldValue::value(
1529                child_value(&ctx, &key).unwrap_or(Value::from(0)),
1530            )))
1531        })
1532    });
1533    // `count(columns:)` counts the rows where those columns are not null, and
1534    // `distinct` counts distinct values among them. Neither is offered where
1535    // the role may name no column: `count` is then a count of rows and nothing
1536    // else, and the corpus tests exactly that -- `'count' has no argument
1537    // named 'columns'`.
1538    if has_rows {
1539        count = count
1540            .argument(InputValue::new(
1541                "columns",
1542                TypeRef::named_nn_list(agg_select_column(base_name)),
1543            ))
1544            .argument(InputValue::new("distinct", TypeRef::named("Boolean")));
1545    }
1546    let mut aggregate_fields = Object::new(&fields_type)
1547        .description(format!("Aggregate functions over {}.", base_name))
1548        .field(count);
1549
1550    for (function, returns, columns) in agg::functions_for(object) {
1551        let function_type = agg::function_fields_type_name(base_name, function);
1552        let mut per_column = Object::new(&function_type).description(format!(
1553            "`{}` of each {} column it applies to.",
1554            function, base_name
1555        ));
1556        for column in &columns {
1557            let column_name = column.clone();
1558            let type_name = agg::field_type_for(object, column, returns);
1559            per_column =
1560                per_column.field(Field::new(column, TypeRef::named(type_name), move |ctx| {
1561                    let column_name = column_name.clone();
1562                    FieldFuture::new(async move {
1563                        Ok(child_value(&ctx, &column_name).map(FieldValue::value))
1564                    })
1565                }));
1566        }
1567        types.push(per_column);
1568
1569        let owned = function.to_string();
1570        aggregate_fields = aggregate_fields.field(Field::new(
1571            function,
1572            TypeRef::named(&function_type),
1573            move |ctx| {
1574                let owned = owned.clone();
1575                FieldFuture::new(async move { Ok(child_of(&ctx, &owned)) })
1576            },
1577        ));
1578    }
1579
1580    types.push(aggregate_fields);
1581    types
1582}
1583
1584/// The column enum a `count(columns:)` draws from.
1585fn agg_select_column(base_name: &str) -> String {
1586    crate::input::order_by::select_column_type_name(base_name)
1587}
1588
1589/// One key of the parent object, as a value.
1590fn child_value(ctx: &ResolverContext<'_>, key: &str) -> Option<Value> {
1591    match ctx.parent_value.as_value() {
1592        Some(Value::Object(map)) => map.get(&async_graphql::Name::new(key)).cloned(),
1593        _ => None,
1594    }
1595}
1596
1597/// One key of the parent object, as a resolvable child.
1598///
1599/// `None` where the key is absent or null, which is what a client that did not
1600/// ask for that aggregate should see.
1601fn child_of<'a>(ctx: &ResolverContext<'_>, key: &str) -> Option<FieldValue<'a>> {
1602    match child_value(ctx, key) {
1603        None | Some(Value::Null) => None,
1604        Some(value) => Some(FieldValue::value(value)),
1605    }
1606}
1607
1608/// An error carrying the code Hasura would give it.
1609///
1610/// The default coding reads the message, and a refusal written here has no
1611/// word in it that a message from PostgreSQL would not also have -- so it says
1612/// so itself rather than being guessed at.
1613fn coded_error(code: &'static str, message: impl Into<String>) -> async_graphql::Error {
1614    let mut error = async_graphql::Error::new(message);
1615    let mut extensions = async_graphql::ErrorExtensionValues::default();
1616    extensions.set("code", code);
1617    error.extensions = Some(extensions);
1618    error
1619}
1620
1621/// A refusal the client could have avoided by writing the request differently.
1622fn validation_error(message: impl Into<String>) -> async_graphql::Error {
1623    coded_error("validation-failed", message)
1624}
1625
1626/// A database error, classified the way Hasura classifies it.
1627///
1628/// PostgreSQL's own message is the whole of what either server says about
1629/// what went wrong; what Hasura adds is a word for *which kind* of wrong,
1630/// taken from the SQLSTATE rather than from the text: `Uniqueness violation.
1631/// duplicate key value violates unique constraint "author_name_key"`. A client
1632/// reporting that to a user is reporting text it already ships.
1633///
1634/// Read from the code, never from the message. The message is localised, the
1635/// code is not, and every attempt to tell these apart by reading English is
1636/// wrong the first time a server runs in another language.
1637fn database_error(error: sqlx::Error) -> async_graphql::Error {
1638    let sqlx::Error::Database(db) = &error else {
1639        return async_graphql::Error::new(error.to_string());
1640    };
1641    let described = match db.code().as_deref() {
1642        // Class 23, integrity constraint violation: the four PostgreSQL
1643        // raises and Hasura names.
1644        Some("23505") => "Uniqueness violation. ",
1645        Some("23502") => "Not-NULL violation. ",
1646        Some("23503") => "Foreign key violation. ",
1647        Some("23514") => "Check constraint violation. ",
1648        // Everything else is answered with PostgreSQL's own words and no
1649        // heading. Inventing one for a class Hasura says nothing about would
1650        // be a difference rather than a match.
1651        _ => "",
1652    };
1653    let error = async_graphql::Error::new(format!("{}{}", described, db.message()));
1654    // And the code, where the SQLSTATE says which one. Only the codes Hasura's
1655    // corpus pins are set: everything else is left for `code_for` to classify,
1656    // which is a guess from the message text and says so. Replacing that guess
1657    // wholesale would be the same guess with fewer places to notice it.
1658    let coded = match db.code().as_deref() {
1659        Some(code) if code.starts_with("23") => Some("constraint-violation"),
1660        // A `LIKE` pattern that ends mid-escape. Not a data exception to
1661        // Hasura -- the pattern came from the request, so the request is what
1662        // was wrong.
1663        Some("22025") => Some("bad-request"),
1664        // A negative `OFFSET`, and the rest of class 22 with it.
1665        Some(code) if code.starts_with("22") => Some("data-exception"),
1666        _ => None,
1667    };
1668    match coded {
1669        Some(code) => at_code(error, code),
1670        None => error,
1671    }
1672}
1673
1674/// The same error, coded as Hasura codes it.
1675fn at_code(mut error: async_graphql::Error, code: &'static str) -> async_graphql::Error {
1676    let mut extensions = error.extensions.take().unwrap_or_default();
1677    extensions.set("code", code);
1678    error.extensions = Some(extensions);
1679    error
1680}
1681
1682/// The same error, told where in the request it happened.
1683///
1684/// Hasura names the place a write went wrong inside the *arguments* --
1685/// `$.selectionSet.insert_author.args.objects[0].bio` -- and a response path
1686/// cannot say that: it names fields of the answer, and the answer has no
1687/// `objects`. So the path is written where the error is raised, by whoever
1688/// knows which row and which column it is about, and
1689/// [`crate::hasura::path_for`] prefers it over the response path.
1690fn at_path(mut error: async_graphql::Error, path: &str) -> async_graphql::Error {
1691    let mut extensions = error.extensions.take().unwrap_or_default();
1692    extensions.set("path", path);
1693    error.extensions = Some(extensions);
1694    error
1695}
1696
1697fn create_object_type(
1698    obj: &TableObjectType,
1699    relationships: &[RelationshipField],
1700    federation_entity: Option<&FederationEntity>,
1701) -> Object {
1702    let mut object = Object::new(&obj.name);
1703    let entity_is_shared = federation_entity.is_some_and(|entity| entity.shared);
1704    if let Some(entity) = federation_entity {
1705        object = object.key(entity.key_fields_sdl());
1706    }
1707
1708    // A table with no comment still gets a description, because Hasura gives
1709    // one and a client generating documentation from the schema would
1710    // otherwise show a blank where it used to show this.
1711    object = match obj.description() {
1712        // An empty description is one that was given and is empty: metadata
1713        // said the type has none, which is not the same as saying nothing.
1714        Some("") => object,
1715        Some(desc) => object.description(desc),
1716        None => object.description(format!(
1717            "columns and relationships of \"{}\"",
1718            obj.table_name()
1719        )),
1720    };
1721
1722    // A GraphQL object may not have two fields of one name, and a schema that
1723    // tries to build one aborts the process rather than returning an error.
1724    // Relationship names are derived from the table they point at, so a
1725    // foreign key column named after its own target -- `pizza.crust`
1726    // referencing `crust`, which is an ordinary way to write that schema --
1727    // produces exactly that clash.
1728    //
1729    // The column wins. It is the table's own data, it is what a client's
1730    // existing queries select, and dropping it to keep a derived name would
1731    // lose something the schema actually says. The relationship is left out
1732    // and said so, which is what Hasura does with the same clash: the
1733    // metadata is marked inconsistent and the field is simply not there.
1734    let mut taken: HashSet<String> = obj.fields.iter().map(|f| f.name.clone()).collect();
1735
1736    // Collected rather than added as they are built, so they can go on in name
1737    // order -- which is the order Hasura answers introspection in, and does not
1738    // depend on which column the catalogue happened to list first.
1739    let mut fields: Vec<(String, Field)> = Vec::new();
1740
1741    for field in &obj.fields {
1742        let field_name = field.name.clone();
1743        let field_type = graphql_type_ref(&field.type_string());
1744        // A document-valued column can be asked for one part of itself, the
1745        // same way `#>` reads one. It is answered here rather than in SQL
1746        // because the same column may be asked for under several aliases --
1747        // `c32_json(path: "a")` beside `c32_json(path: "arr[0]")` -- and
1748        // one projection cannot carry both.
1749        let takes_path = matches!(&field.graphql_type, crate::types::GraphQLType::Json)
1750            || matches!(
1751                &field.graphql_type,
1752                crate::types::GraphQLType::Custom(name) if name == "json"
1753            );
1754
1755        // Create field with resolver that extracts from parent async_graphql::Value
1756        // The query resolver stores rows as FieldValue::value(Value::Object)
1757        // so we use as_value() to get the Value and extract fields from the Object
1758        let gql_field = Field::new(&field.name, field_type, move |ctx| {
1759            let field_name = field_name.clone();
1760            FieldFuture::new(async move {
1761                // Get the parent value as async_graphql::Value using as_value()
1762                if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1763                    // Convert field name to async_graphql::Name for lookup
1764                    let key = async_graphql::Name::new(&field_name);
1765                    let path = match takes_path {
1766                        true => ctx.args.get("path").and_then(|v| v.string().ok()),
1767                        false => None,
1768                    };
1769                    match map.get(&key) {
1770                        // A null is the answer, not a value to resolve. An
1771                        // enum-typed column said so with `internal: invalid
1772                        // item for enum` rather than answering null, because a
1773                        // null is not one of its members.
1774                        Some(Value::Null) => return Ok(None),
1775                        Some(val) => {
1776                            let val = match path {
1777                                Some(path) => walk_json_path(val, path)?,
1778                                None => val.clone(),
1779                            };
1780                            return match val {
1781                                Value::Null => Ok(None),
1782                                val => Ok(Some(FieldValue::value(val))),
1783                            };
1784                        }
1785                        None => {}
1786                    }
1787                }
1788
1789                // Field not found or parent not a Value::Object
1790                Ok(None)
1791            })
1792        });
1793
1794        let gql_field = match takes_path {
1795            true => gql_field.argument(
1796                InputValue::new("path", TypeRef::named("String")).description("JSON select path"),
1797            ),
1798            false => gql_field,
1799        };
1800
1801        // A computed field whose function takes more than the row: the
1802        // caller writes those under `args`, for the reason a computed
1803        // relationship's are written there.
1804        let gql_field = match field.arguments.is_empty() {
1805            true => gql_field,
1806            false => gql_field.argument(InputValue::new(
1807                "args",
1808                TypeRef::named_nn(computed_args_type_name(&obj.local_name, &field.name)),
1809            )),
1810        };
1811
1812        let gql_field = if let Some(desc) = &field.description {
1813            gql_field.description(desc)
1814        } else {
1815            gql_field
1816        };
1817        let gql_field = if entity_is_shared && !field.is_pk {
1818            gql_field.shareable()
1819        } else {
1820            gql_field
1821        };
1822
1823        fields.push((field.name.clone(), gql_field));
1824    }
1825
1826    // Relationship fields. The query resolver embeds related rows into the
1827    // parent JSON before returning it, so these read from the parent value the
1828    // same way column fields do.
1829    for rel in relationships {
1830        if !taken.insert(rel.name.clone()) {
1831            tracing::warn!(
1832                "{}: not exposing the relationship to \"{}\" as `{}` -- the table \
1833                 already has a column of that name",
1834                obj.name,
1835                rel.target_type,
1836                rel.name
1837            );
1838            continue;
1839        }
1840        let field_name = rel.name.clone();
1841        let field_type = if rel.is_list {
1842            TypeRef::named_nn_list_nn(&rel.target_type)
1843        } else if always_present(rel, &obj.table) {
1844            TypeRef::named_nn(&rel.target_type)
1845        } else {
1846            TypeRef::named(&rel.target_type)
1847        };
1848
1849        let is_list = rel.is_list;
1850        let mut gql_field = Field::new(&rel.name, field_type, move |ctx| {
1851            let field_name = field_name.clone();
1852            FieldFuture::new(async move {
1853                if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1854                    let key = async_graphql::Name::new(&field_name);
1855                    match map.get(&key) {
1856                        // A row that points at nothing has no related row, and
1857                        // the answer to that is null -- not an object whose
1858                        // every field is null, which is what handing a null
1859                        // parent value to the field resolvers produced.
1860                        Some(Value::Null) if !is_list => return Ok(None),
1861                        Some(val) => return Ok(Some(FieldValue::value(val.clone()))),
1862                        None => {}
1863                    }
1864                }
1865                Ok(None)
1866            })
1867        });
1868
1869        // The same arguments the root field takes. An embedded list is a list
1870        // like any other: a client showing the five most recent articles of an
1871        // author has nowhere else to say so, and without these the only way to
1872        // narrow one is to fetch all of it and discard the rest in the client.
1873        // A to-one relationship is left alone -- there is nothing to order or
1874        // page through when the answer is one row.
1875        // A computed relationship's function may take more than the row --
1876        // "the articles of this author matching a search" -- and `args` is
1877        // where the caller writes them, so a term called `limit` cannot shadow
1878        // the one that pages the result.
1879        if !rel.arguments.is_empty() {
1880            gql_field = gql_field.argument(InputValue::new(
1881                "args",
1882                TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1883            ));
1884        }
1885        if rel.is_list {
1886            gql_field = gql_field
1887                .argument(InputValue::new(
1888                    "distinct_on",
1889                    TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1890                        &rel.target_local_name,
1891                    )),
1892                ))
1893                .argument(InputValue::new(
1894                    "where",
1895                    TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1896                        &rel.target_local_name,
1897                    )),
1898                ))
1899                .argument(InputValue::new(
1900                    "order_by",
1901                    TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1902                        &rel.target_local_name,
1903                    )),
1904                ))
1905                .argument(InputValue::new("limit", TypeRef::named("Int")))
1906                .argument(InputValue::new("offset", TypeRef::named("Int")));
1907        }
1908
1909        let gql_field = if let Some(desc) = &rel.description {
1910            gql_field.description(desc)
1911        } else {
1912            gql_field
1913        };
1914        let gql_field = if entity_is_shared {
1915            gql_field.shareable()
1916        } else {
1917            gql_field
1918        };
1919
1920        fields.push((rel.name.clone(), gql_field));
1921
1922        // `author { articles_aggregate { aggregate { count } } }` -- the count
1923        // of a row's children without fetching them, which is the query behind
1924        // every "12 comments" beside a post. Only for a relationship to many:
1925        // counting one row is not a question anyone asks.
1926        if rel.is_list {
1927            let aggregate_field = format!("{}_aggregate", rel.name);
1928            if taken.insert(aggregate_field.clone()) {
1929                let key = aggregate_field.clone();
1930                fields.push((
1931                    aggregate_field.clone(),
1932                    Field::new(
1933                        &aggregate_field,
1934                        TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
1935                            &rel.target_local_name,
1936                        )),
1937                        move |ctx| {
1938                            let key = key.clone();
1939                            FieldFuture::new(async move {
1940                                // Absent where the parent has no children at
1941                                // all; the aggregate type's own resolvers read
1942                                // a missing key as zero and an empty list.
1943                                Ok(Some(FieldValue::value(
1944                                    child_value(&ctx, &key).unwrap_or(Value::Null),
1945                                )))
1946                            })
1947                        },
1948                    )
1949                    // The same arguments the list itself takes. "How many of
1950                    // this author's articles were published this year" is the
1951                    // question a count is usually asked as, and without a
1952                    // `where` there is no way to write it.
1953                    .argument(InputValue::new(
1954                        "distinct_on",
1955                        TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1956                            &rel.target_local_name,
1957                        )),
1958                    ))
1959                    .argument(InputValue::new(
1960                        "where",
1961                        TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1962                            &rel.target_local_name,
1963                        )),
1964                    ))
1965                    .argument(InputValue::new(
1966                        "order_by",
1967                        TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1968                            &rel.target_local_name,
1969                        )),
1970                    ))
1971                    .argument(InputValue::new("limit", TypeRef::named("Int")))
1972                    .argument(InputValue::new("offset", TypeRef::named("Int")))
1973                    .description(format!("Aggregates over {}.", rel.name)),
1974                ));
1975                // Counting the rows a function answers with means calling it,
1976                // so the aggregate takes whatever the function takes.
1977                if !rel.arguments.is_empty() {
1978                    let last = fields.len() - 1;
1979                    let (name, field) = fields.remove(last);
1980                    fields.push((
1981                        name,
1982                        field.argument(InputValue::new(
1983                            "args",
1984                            TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1985                        )),
1986                    ));
1987                }
1988                if entity_is_shared {
1989                    let last = fields.len() - 1;
1990                    let (name, field) = fields.remove(last);
1991                    fields.push((name, field.shareable()));
1992                }
1993            }
1994        }
1995    }
1996
1997    fields.sort_by(|(a, _), (b, _)| a.cmp(b));
1998    for (_, field) in fields {
1999        object = object.field(field);
2000    }
2001
2002    object
2003}
2004
2005/// A root field, whichever root it is on.
2006///
2007/// A query field and a subscription field take the same arguments and mean the
2008/// same by them -- the difference is only whether the answer is sent once or
2009/// whenever it changes -- and the two builders share no trait of their own, so
2010/// this is what lets the argument list be written once.
2011trait RootField: Sized {
2012    fn with_argument(self, value: InputValue) -> Self;
2013}
2014
2015impl RootField for Field {
2016    fn with_argument(self, value: InputValue) -> Self {
2017        self.argument(value)
2018    }
2019}
2020
2021impl RootField for SubscriptionField {
2022    fn with_argument(self, value: InputValue) -> Self {
2023        self.argument(value)
2024    }
2025}
2026
2027/// The five arguments that narrow a set of rows, in the order Hasura lists
2028/// them.
2029fn with_row_arguments<F: RootField>(field: F, type_name: &str) -> F {
2030    with_row_arguments_named(field, type_name, true)
2031}
2032
2033/// The same, where `columns` says whether the table has any to name.
2034///
2035/// A table a role may count and not read has none: there is no column enum to
2036/// order by and none to be distinct on, so those two arguments are not there
2037/// -- and could not be, since the types they name are not built either. What
2038/// is left still narrows the set being counted, which is the whole of what
2039/// such a root is for.
2040fn with_row_arguments_named<F: RootField>(field: F, type_name: &str, columns: bool) -> F {
2041    let mut field = field;
2042    if columns {
2043        field = field.with_argument(InputValue::new(
2044            "distinct_on",
2045            TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(type_name)),
2046        ));
2047    }
2048    field = field
2049        .with_argument(InputValue::new("limit", TypeRef::named("Int")))
2050        .with_argument(InputValue::new("offset", TypeRef::named("Int")));
2051    if columns {
2052        field = field.with_argument(InputValue::new(
2053            "order_by",
2054            TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(type_name)),
2055        ));
2056    }
2057    field.with_argument(InputValue::new(
2058        "where",
2059        TypeRef::named(crate::input::bool_exp::bool_exp_type_name(type_name)),
2060    ))
2061}
2062
2063/// One argument per primary key column, named and typed after the column
2064/// itself rather than assuming an integer `id`.
2065fn with_key_arguments<F: RootField>(
2066    field: F,
2067    schema_name: &str,
2068    table_name: &str,
2069    pk_columns: &[(String, String)],
2070    names: &crate::names::NameOverrides,
2071) -> F {
2072    let mut field = field;
2073    for (column, pg_type) in pk_columns {
2074        field = field.with_argument(InputValue::new(
2075            names
2076                .column(schema_name, table_name, column)
2077                .unwrap_or(column),
2078            TypeRef::named_nn(exposed_pk_argument_type(
2079                names,
2080                schema_name,
2081                table_name,
2082                column,
2083                pg_type,
2084            )),
2085        ));
2086    }
2087    field
2088}
2089
2090/// Create the Query type with all table query fields.
2091fn create_query_type(
2092    generated: &GeneratedSchema,
2093    root_name: &str,
2094    max_rows: Option<i64>,
2095    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2096    names: Arc<crate::names::NameOverrides>,
2097) -> Object {
2098    // Collected rather than added as they are built, so they can go on in name
2099    // order: that is the order Hasura answers introspection in, and it is the
2100    // order a client diffing two schemas, or a generator writing its types
2101    // out, gets a stable answer from.
2102    let mut roots: Vec<(String, Field)> = Vec::new();
2103
2104    for field in &generated.query_fields {
2105        let table_name = field.table_name.clone();
2106        let schema_name = field.schema_name.clone();
2107        let type_name = field.type_name.clone();
2108        let local_type_name = field.local_type_name.clone();
2109        let is_by_pk = field.is_by_pk;
2110        let pk_columns = field.pk_columns.clone();
2111        let return_type = graphql_type_ref(&field.return_type);
2112
2113        let spec_type_name = local_type_name.clone();
2114        let spec = Arc::new(QueryFieldSpec {
2115            schema_name,
2116            table_name,
2117            type_name,
2118            is_by_pk,
2119            pk_columns: pk_columns.clone(),
2120            max_rows,
2121            relationships: Arc::clone(&relationships),
2122            names: Arc::clone(&names),
2123            call: None,
2124        });
2125
2126        let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2127            let spec = Arc::clone(&spec);
2128            FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
2129        });
2130
2131        // Add standard query arguments
2132        gql_field = match is_by_pk {
2133            false => with_row_arguments(gql_field, &spec_type_name),
2134            true => with_key_arguments(
2135                gql_field,
2136                &field.schema_name,
2137                &field.table_name,
2138                &pk_columns,
2139                names.as_ref(),
2140            ),
2141        };
2142
2143        if let Some(desc) = &field.description {
2144            gql_field = gql_field.description(desc);
2145        }
2146
2147        // A table this role may count and not read has no row type, so the
2148        // list root that would answer with one is not built. The entry is
2149        // still here for the aggregate root below, which is what such a role
2150        // was granted.
2151        if field.rows {
2152            roots.push((field.name.clone(), gql_field));
2153        }
2154
2155        // The same rows, with numbers about them. Same arguments as the list
2156        // field, because `author_aggregate(where: ...)` counts the set the
2157        // filter describes, not the whole table.
2158        //
2159        // Not built at all for a role whose permission withholds it: counting
2160        // rows is a way of learning about them, so it is granted separately
2161        // from reading them and refused by absence rather than at execution.
2162        if !is_by_pk && field.aggregates {
2163            let agg_spec = Arc::new(AggregateSpec {
2164                schema_name: field.schema_name.clone(),
2165                table_name: field.table_name.clone(),
2166                type_name: field.type_name.clone(),
2167                max_rows,
2168                relationships: Arc::clone(&relationships),
2169                names: Arc::clone(&names),
2170                call: None,
2171            });
2172            let aggregate_field_name = field.aggregate_name.clone().unwrap_or_else(|| {
2173                crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2174            });
2175            let aggregate_field_name_for_sorting = aggregate_field_name.clone();
2176            let mut agg_field = Field::new(
2177                aggregate_field_name,
2178                TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2179                    &field.local_type_name,
2180                )),
2181                move |ctx| {
2182                    let agg_spec = Arc::clone(&agg_spec);
2183                    FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
2184                },
2185            );
2186            agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2187            agg_field = match field.aggregate_description.as_deref() {
2188                Some("") => agg_field,
2189                Some(given) => agg_field.description(given),
2190                None => agg_field.description(format!(
2191                    "fetch aggregated fields from the table: \"{}\"",
2192                    field.table_name
2193                )),
2194            };
2195            roots.push((aggregate_field_name_for_sorting, agg_field));
2196        }
2197    }
2198
2199    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2200    let mut query = Object::new(root_name);
2201    // A GraphQL object may not have no fields, so a schema that exposes no
2202    // table needs something on its query root. Hasura puts a placeholder there
2203    // and calls it this; a client that reaches it has nothing to read.
2204    if roots.is_empty() {
2205        return query.field(
2206            Field::new("no_queries_available", TypeRef::named("String"), |_| {
2207                FieldFuture::new(async move { Ok(None::<Value>) })
2208            })
2209            .description("There are no queries available to the current role."),
2210        );
2211    }
2212    for (_, field) in roots {
2213        query = query.field(field);
2214    }
2215
2216    query
2217}
2218
2219/// One document operator: its name, and the type its columns are given.
2220type JsonbOperator = (&'static str, fn() -> TypeRef);
2221
2222/// What a document column may be told to do, and what each is told with.
2223///
2224/// `_append` and `_prepend` take another document; `_delete_key` takes a key;
2225/// `_delete_elem` takes an index into an array; `_delete_at_path` takes the
2226/// path to what is being removed. The shapes are per operator rather than per
2227/// column, which is why one input type per operator says all of it.
2228const JSONB_OPERATORS: &[JsonbOperator] = &[
2229    ("_append", || TypeRef::named("jsonb")),
2230    ("_delete_at_path", || TypeRef::named_nn_list("String")),
2231    ("_delete_elem", || TypeRef::named("Int")),
2232    ("_delete_key", || TypeRef::named("String")),
2233    ("_prepend", || TypeRef::named("jsonb")),
2234];
2235
2236/// The name of the input holding one document operator's columns.
2237fn jsonb_operator_input(type_name: &str, operator: &str) -> String {
2238    format!("{}{}_input", type_name, operator)
2239}
2240
2241/// Add the operators an update may be written with.
2242///
2243/// All optional, and at least one required -- which GraphQL cannot express, so
2244/// the resolver says so instead of the schema. Making `_set` non-null would
2245/// have been expressible and wrong: an update that only increments a counter
2246/// never sends one.
2247fn with_update_operators(
2248    field: Field,
2249    base_name: &str,
2250    has_numeric: bool,
2251    has_jsonb: bool,
2252) -> Field {
2253    let mut field = field;
2254    // A table with no document column is not offered the document operators:
2255    // there is nothing there to append to.
2256    if has_jsonb {
2257        for (operator, _) in JSONB_OPERATORS {
2258            field = field.argument(InputValue::new(
2259                *operator,
2260                TypeRef::named(jsonb_operator_input(base_name, operator)),
2261            ));
2262        }
2263    }
2264    if has_numeric {
2265        field = field.argument(InputValue::new(
2266            "_inc",
2267            TypeRef::named(format!("{}_inc_input", base_name)),
2268        ));
2269    }
2270    field.argument(InputValue::new(
2271        "_set",
2272        TypeRef::named(format!("{}_set_input", base_name)),
2273    ))
2274}
2275
2276/// Create the Mutation type with all mutation fields.
2277/// Which columns a write may name, and so which of its inputs exist.
2278///
2279/// One definition, called both where the inputs are registered and where the
2280/// arguments naming them are declared. They used to agree by coincidence --
2281/// both read the object type's fields -- and stopped the moment a write input
2282/// began following its own permission. An argument naming a type nobody
2283/// registered is a schema async-graphql refuses to build, which costs a role
2284/// every field it has over the one it cannot use.
2285fn write_fields<'a>(
2286    object: &'a crate::schema::object::TableObjectType,
2287    names: &crate::names::NameOverrides,
2288    role: Option<&str>,
2289    verb: crate::role::Verb,
2290) -> Vec<&'a crate::schema::object::GraphQLField> {
2291    let allowed = role.and_then(|role| {
2292        names
2293            .permissions(&object.table.schema, &object.table.name, role)
2294            .and_then(|granted| granted.write_columns(verb))
2295    });
2296    object
2297        .writable_fields
2298        .iter()
2299        .filter(|field| {
2300            let column = table_column_for(names, &object.table, &field.name);
2301            // A column PostgreSQL generates always is not one a write may
2302            // name. Leaving it in the input type moved the refusal to the
2303            // database, which answers `cannot insert a non-DEFAULT value into
2304            // column "id"` and has by then forgotten which argument the value
2305            // came from. Out of the type, the walk over the request says
2306            // `field 'id' not found in type: 'author_insert_input'` and can
2307            // still point at it.
2308            object
2309                .table
2310                .get_column(column)
2311                .is_some_and(|c| !c.always_generated)
2312                && allowed.is_none_or(|set| set.allows(column))
2313        })
2314        .collect()
2315}
2316
2317/// Whether a type has an `_inc_input` and a set of document operator inputs.
2318fn update_inputs(
2319    object: &crate::schema::object::TableObjectType,
2320    names: &crate::names::NameOverrides,
2321    role: Option<&str>,
2322) -> (bool, bool) {
2323    let fields = write_fields(object, names, role, crate::role::Verb::Update);
2324    (
2325        fields
2326            .iter()
2327            .any(|f| crate::schema::aggregate::is_incrementable(&f.graphql_type)),
2328        fields
2329            .iter()
2330            .any(|f| matches!(&f.graphql_type, crate::types::GraphQLType::Json)),
2331    )
2332}
2333
2334fn create_mutation_type(
2335    generated: &GeneratedSchema,
2336    root_name: &str,
2337    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2338    type_names: Arc<HashMap<(String, String), String>>,
2339    names: Arc<crate::names::NameOverrides>,
2340    max_rows: Option<i64>,
2341    role: Option<&str>,
2342) -> Object {
2343    let mut mutation = Object::new(root_name);
2344    // In name order, for the reason the query root is: see there.
2345    let mut roots: Vec<(String, Field)> = Vec::new();
2346
2347    // Only a table with a unique constraint has a conflict to name, and only
2348    // those got the types for it.
2349    // Which tables have something to add to, and which have a document to
2350    // operate on. Read from what was registered above rather than worked out
2351    // again from the object types: the second answer is the one that can
2352    // drift, and drifting means an argument naming a type that is not there.
2353    let mut has_numeric_column: HashSet<String> = HashSet::new();
2354    let mut has_jsonb_column: HashSet<String> = HashSet::new();
2355    for object in generated.object_types.values() {
2356        let (numeric, jsonb) = update_inputs(object, &names, role);
2357        if numeric {
2358            has_numeric_column.insert(object.local_name.clone());
2359        }
2360        if jsonb {
2361            has_jsonb_column.insert(object.local_name.clone());
2362        }
2363    }
2364
2365    let has_conflict_target: HashSet<String> = generated
2366        .object_types
2367        .iter()
2368        .filter(|(_, object)| !object.table.unique_constraints.is_empty())
2369        .map(|(_, object)| object.local_name.clone())
2370        .collect();
2371
2372    for field in &generated.mutation_fields {
2373        let table_name = field.table_name.clone();
2374        let schema_name = field.schema_name.clone();
2375        let mutation_type = field.mutation_type;
2376        let pk_columns = field.pk_columns.clone();
2377        let return_type = graphql_type_ref(&field.return_type);
2378
2379        // The table's own type name, which is what its boolean expression is
2380        // named after. A bulk mutation returns `[author!]!` and a by-key one
2381        // returns `author`; both name the same table.
2382        let where_type = field.local_type_name.clone();
2383
2384        let resolver_pk_columns = pk_columns.clone();
2385        let field_relationships = Arc::clone(&relationships);
2386        let field_type_names = Arc::clone(&type_names);
2387        let field_names = Arc::clone(&names);
2388        let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2389            let table_name = table_name.clone();
2390            let schema_name = schema_name.clone();
2391            let pk_columns = resolver_pk_columns.clone();
2392            let relationships = Arc::clone(&field_relationships);
2393            let type_names = Arc::clone(&field_type_names);
2394            let names = Arc::clone(&field_names);
2395            FieldFuture::new(async move {
2396                resolve_mutation(
2397                    &ctx,
2398                    &schema_name,
2399                    &table_name,
2400                    mutation_type,
2401                    &pk_columns,
2402                    &relationships,
2403                    &type_names,
2404                    &names,
2405                    max_rows,
2406                )
2407                .await
2408            })
2409        });
2410
2411        // Add mutation-specific arguments.
2412        //
2413        // A by-PK mutation takes the key columns rather than a `where` object:
2414        // it is meant to address exactly one row, and accepting `where` made it
2415        // an ordinary bulk mutation that happened to return the first result.
2416        match mutation_type {
2417            MutationType::Insert | MutationType::InsertOne => {
2418                let insert_input = format!("{}_insert_input", where_type);
2419                gql_field = if mutation_type == MutationType::Insert {
2420                    gql_field.argument(InputValue::new(
2421                        "objects",
2422                        TypeRef::named_nn_list_nn(&insert_input),
2423                    ))
2424                } else {
2425                    // A single insert takes `object`, not a one-element
2426                    // `objects`.
2427                    gql_field.argument(InputValue::new("object", TypeRef::named_nn(&insert_input)))
2428                };
2429                if has_conflict_target.contains(&where_type) {
2430                    gql_field = gql_field.argument(InputValue::new(
2431                        "on_conflict",
2432                        TypeRef::named(format!("{}_on_conflict", where_type)),
2433                    ));
2434                }
2435            }
2436            MutationType::UpdateByPk => {
2437                gql_field = gql_field.argument(InputValue::new(
2438                    "pk_columns",
2439                    TypeRef::named_nn(format!("{}_pk_columns_input", where_type)),
2440                ));
2441                gql_field = with_update_operators(
2442                    gql_field,
2443                    &where_type,
2444                    has_numeric_column.contains(&where_type),
2445                    has_jsonb_column.contains(&where_type),
2446                );
2447            }
2448            MutationType::Update => {
2449                gql_field = gql_field.argument(InputValue::new(
2450                    "where",
2451                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2452                ));
2453                gql_field = with_update_operators(
2454                    gql_field,
2455                    &where_type,
2456                    has_numeric_column.contains(&where_type),
2457                    has_jsonb_column.contains(&where_type),
2458                );
2459            }
2460            MutationType::UpdateMany => {
2461                gql_field = gql_field.argument(InputValue::new(
2462                    "updates",
2463                    TypeRef::named_nn_list_nn(format!("{}_updates", where_type)),
2464                ));
2465            }
2466            MutationType::DeleteByPk => {
2467                for (col_name, pg_type) in &pk_columns {
2468                    gql_field = gql_field.argument(InputValue::new(
2469                        names
2470                            .column(&field.schema_name, &field.table_name, col_name)
2471                            .unwrap_or(col_name),
2472                        TypeRef::named_nn(exposed_pk_argument_type(
2473                            names.as_ref(),
2474                            &field.schema_name,
2475                            &field.table_name,
2476                            col_name,
2477                            pg_type,
2478                        )),
2479                    ));
2480                }
2481            }
2482            MutationType::Delete => {
2483                // Required, as it is on an update: a delete with no predicate
2484                // is a delete of the whole table, and this refused one at
2485                // execution while the schema said it was a query worth
2486                // writing. Saying so in the type is what Hasura does, and it
2487                // is what a client's own tooling can catch before the request
2488                // is sent.
2489                gql_field = gql_field.argument(InputValue::new(
2490                    "where",
2491                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2492                ));
2493            }
2494        }
2495
2496        if let Some(desc) = &field.description {
2497            gql_field = gql_field.description(desc);
2498        }
2499
2500        roots.push((field.name.clone(), gql_field));
2501    }
2502
2503    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2504    for (_, field) in roots {
2505        mutation = mutation.field(field);
2506    }
2507
2508    mutation
2509}
2510
2511/// Create the Subscription type: the query root, answered again on change.
2512///
2513/// A subscription here is a **live query**. It carries the same fields the
2514/// query root does, takes the same arguments, and answers with the same rows
2515/// -- the difference is that the answer is sent again whenever it stops being
2516/// true. That is the contract a client generated against Hasura expects, and
2517/// the reason its `subscription_root` is a mirror of its `query_root`.
2518///
2519/// What wakes it is this server's own: a trigger publishes on the changed
2520/// table and the query is read again, which costs nothing while nothing is
2521/// written and answers in milliseconds when something is. A trigger cannot
2522/// see everything a query can -- a view has none, an embedded row may live in
2523/// a table that carries none, and `where: {expires_at: {_lt: "now()"}}`
2524/// changes with no write at all -- so a slow refresh runs beside it and
2525/// closes exactly those gaps. Polling alone is what Hasura does, and it pays
2526/// for every subscriber every tick whether or not anything happened.
2527fn create_subscription_type(
2528    generated: &GeneratedSchema,
2529    root_name: &str,
2530    max_rows: Option<i64>,
2531    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2532    names: Arc<crate::names::NameOverrides>,
2533    refresh: std::time::Duration,
2534) -> Subscription {
2535    let mut roots: Vec<(String, SubscriptionField)> = Vec::new();
2536
2537    for field in &generated.query_fields {
2538        let spec_type_name = field.local_type_name.clone();
2539        let pk_columns = field.pk_columns.clone();
2540        let is_by_pk = field.is_by_pk;
2541        let return_type = graphql_type_ref(&field.return_type);
2542        let watched = vec![crate::subscription::table_channel_name(
2543            &field.schema_name,
2544            &field.table_name,
2545        )];
2546
2547        let spec = Arc::new(QueryFieldSpec {
2548            schema_name: field.schema_name.clone(),
2549            table_name: field.table_name.clone(),
2550            type_name: field.type_name.clone(),
2551            is_by_pk,
2552            pk_columns: pk_columns.clone(),
2553            max_rows,
2554            relationships: Arc::clone(&relationships),
2555            names: Arc::clone(&names),
2556            call: None,
2557        });
2558
2559        let mut gql_field = SubscriptionField::new(&field.name, return_type, move |ctx| {
2560            let spec = Arc::clone(&spec);
2561            let watched = watched.clone();
2562            SubscriptionFieldFuture::new(async move {
2563                let wake = wake_on(&ctx, &watched, refresh).await;
2564                Ok(live(ctx, LiveSource::Rows(spec), wake))
2565            })
2566        });
2567
2568        gql_field = match is_by_pk {
2569            false => with_row_arguments(gql_field, &spec_type_name),
2570            true => with_key_arguments(
2571                gql_field,
2572                &field.schema_name,
2573                &field.table_name,
2574                &pk_columns,
2575                names.as_ref(),
2576            ),
2577        };
2578        if let Some(desc) = &field.description {
2579            gql_field = gql_field.description(desc);
2580        }
2581        // No row type, no root that answers with one -- see the query root.
2582        if field.rows {
2583            roots.push((field.name.clone(), gql_field));
2584        }
2585
2586        // A live query mirrors the query root, so an aggregate the role may not
2587        // ask for once is not one it may ask for continuously either.
2588        if is_by_pk || !field.aggregates {
2589            continue;
2590        }
2591
2592        // The same rows, with numbers about them, watched the same way.
2593        let agg_spec = Arc::new(AggregateSpec {
2594            schema_name: field.schema_name.clone(),
2595            table_name: field.table_name.clone(),
2596            type_name: field.type_name.clone(),
2597            max_rows,
2598            relationships: Arc::clone(&relationships),
2599            names: Arc::clone(&names),
2600            call: None,
2601        });
2602        let agg_watched = vec![crate::subscription::table_channel_name(
2603            &field.schema_name,
2604            &field.table_name,
2605        )];
2606        let agg_name = field.aggregate_name.clone().unwrap_or_else(|| {
2607            crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2608        });
2609        let mut agg_field = SubscriptionField::new(
2610            agg_name.clone(),
2611            TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2612                &field.local_type_name,
2613            )),
2614            move |ctx| {
2615                let agg_spec = Arc::clone(&agg_spec);
2616                let watched = agg_watched.clone();
2617                SubscriptionFieldFuture::new(async move {
2618                    let wake = wake_on(&ctx, &watched, refresh).await;
2619                    Ok(live(ctx, LiveSource::Aggregate(agg_spec), wake))
2620                })
2621            },
2622        );
2623        agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2624        agg_field = match field.aggregate_description.as_deref() {
2625            Some("") => agg_field,
2626            Some(given) => agg_field.description(given),
2627            None => agg_field.description(format!(
2628                "fetch aggregated fields from the table: \"{}\"",
2629                field.table_name
2630            )),
2631        };
2632        roots.push((agg_name, agg_field));
2633    }
2634
2635    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2636    let mut subscription = Subscription::new(root_name);
2637    // A GraphQL object may not have no fields, and a schema that exposes no
2638    // table still has a subscription root if subscriptions are on.
2639    if roots.is_empty() {
2640        return subscription.field(SubscriptionField::new(
2641            "no_subscriptions_available",
2642            TypeRef::named("String"),
2643            |_| {
2644                SubscriptionFieldFuture::new(async move {
2645                    Ok(futures::stream::empty::<
2646                        Result<FieldValue, async_graphql::Error>,
2647                    >())
2648                })
2649            },
2650        ));
2651    }
2652    for (_, field) in roots {
2653        subscription = subscription.field(field);
2654    }
2655    subscription
2656}
2657
2658/// What tells a live query its answer may have changed.
2659///
2660/// Every notification on the tables it reads, and a slow tick beside them.
2661/// Yields once per wake and never ends -- a subscription lasts as long as the
2662/// client holds it open.
2663async fn wake_on(
2664    ctx: &ResolverContext<'_>,
2665    channels: &[String],
2666    refresh: std::time::Duration,
2667) -> futures::stream::BoxStream<'static, ()> {
2668    use futures::StreamExt;
2669
2670    let mut streams: Vec<futures::stream::BoxStream<'static, ()>> =
2671        Vec::with_capacity(channels.len() + 1);
2672    // A tick even where nothing is listening: a table with no trigger, a view,
2673    // and a predicate that changes with the clock all depend on it.
2674    streams.push(
2675        tokio_stream::wrappers::IntervalStream::new({
2676            let mut interval = tokio::time::interval(refresh);
2677            interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
2678            interval
2679        })
2680        .map(|_| ())
2681        .boxed(),
2682    );
2683
2684    if let Ok(broker_arc) = ctx.data::<Arc<RwLock<Option<NotifyBroker>>>>() {
2685        let guard = broker_arc.read().await;
2686        if let Some(broker) = guard.as_ref() {
2687            for channel in channels {
2688                // `subscribe_or_create` rather than `subscribe`: a table whose
2689                // trigger was never installed has no channel, and a live query
2690                // on it is answered by the refresh rather than refused.
2691                streams.push(
2692                    broker
2693                        .subscribe_or_create(channel)
2694                        .await
2695                        .map(|_| ())
2696                        .boxed(),
2697                );
2698            }
2699        }
2700    }
2701
2702    futures::stream::select_all(streams).boxed()
2703}
2704
2705/// What a live query reads each time it is woken.
2706///
2707/// An enum rather than a closure because the answer borrows the context it is
2708/// read from, and a closure returning a future that borrows its argument is
2709/// not something a bound can say.
2710enum LiveSource {
2711    Rows(Arc<QueryFieldSpec>),
2712    Aggregate(Arc<AggregateSpec>),
2713}
2714
2715/// Read one answer, and the value to compare it against the last one by.
2716async fn read_live<'a>(
2717    ctx: &ResolverContext<'a>,
2718    source: &LiveSource,
2719) -> Result<(serde_json::Value, Option<FieldValue<'a>>), async_graphql::Error> {
2720    match source {
2721        LiveSource::Rows(spec) => {
2722            let rows = query_rows(ctx, spec).await?;
2723            Ok((
2724                serde_json::Value::Array(rows.clone()),
2725                rows_as_field_value(rows, spec.is_by_pk),
2726            ))
2727        }
2728        LiveSource::Aggregate(spec) => {
2729            let value = aggregate_value(ctx, spec).await?;
2730            let seen = serde_json::to_value(&value).unwrap_or_default();
2731            Ok((seen, Some(FieldValue::value(value))))
2732        }
2733    }
2734}
2735
2736/// A live query: the answer now, and the answer again each time it changes.
2737///
2738/// The first item is sent as soon as the client subscribes, which is what
2739/// makes this a query rather than a feed -- there is no window in which the
2740/// client knows nothing. After that, `answer` is read again on every wake and
2741/// sent only when what came back is not what was sent last: a write that
2742/// changes a row the subscription does not select is a wake and not a message.
2743fn live<'a>(
2744    ctx: ResolverContext<'a>,
2745    source: LiveSource,
2746    wake: futures::stream::BoxStream<'static, ()>,
2747) -> impl futures::Stream<Item = Result<FieldValue<'a>, async_graphql::Error>> + Send + 'a {
2748    use futures::StreamExt;
2749
2750    struct Live<'a> {
2751        ctx: ResolverContext<'a>,
2752        source: LiveSource,
2753        wake: futures::stream::BoxStream<'static, ()>,
2754        sent: Option<serde_json::Value>,
2755        started: bool,
2756        /// An error has been sent, and the subscription ends after it.
2757        failed: bool,
2758        /// Counts this subscription as open for as long as its stream lives.
2759        _active: crate::subscription::ActiveSubscription,
2760    }
2761
2762    futures::stream::unfold(
2763        Live {
2764            ctx,
2765            source,
2766            wake,
2767            sent: None,
2768            started: false,
2769            failed: false,
2770            _active: crate::subscription::ActiveSubscription::open(),
2771        },
2772        |mut live: Live<'a>| async move {
2773            if live.failed {
2774                return None;
2775            }
2776            loop {
2777                if live.started {
2778                    live.wake.next().await?;
2779                } else {
2780                    live.started = true;
2781                }
2782                let read = read_live(&live.ctx, &live.source).await;
2783                match read {
2784                    // An error ends the subscription: the client asked
2785                    // something that cannot be answered, and answering it
2786                    // again on the next tick would only repeat that. It is
2787                    // sent first, though -- ending on it unsent, as a
2788                    // `take_while(is_ok)` here once did, answers a refused
2789                    // subscription with a bare `complete` that reads exactly
2790                    // like one that finished.
2791                    Err(e) => {
2792                        live.failed = true;
2793                        return Some((Err(e), live));
2794                    }
2795                    Ok((seen, value)) => {
2796                        if live.sent.as_ref() == Some(&seen) {
2797                            continue;
2798                        }
2799                        live.sent = Some(seen);
2800                        let value = value.unwrap_or_else(|| FieldValue::value(Value::Null));
2801                        return Some((Ok(value), live));
2802                    }
2803                }
2804            }
2805        },
2806    )
2807}
2808
2809/// Everything a query field's resolver needs about the field it serves.
2810struct QueryFieldSpec {
2811    schema_name: String,
2812    table_name: String,
2813    type_name: String,
2814    is_by_pk: bool,
2815    pk_columns: Vec<(String, String)>,
2816    max_rows: Option<i64>,
2817    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2818    names: Arc<crate::names::NameOverrides>,
2819    /// Set where the rows come from a function rather than from the table
2820    /// itself. Everything else about reading them is the same.
2821    call: Option<Arc<FunctionCall>>,
2822}
2823
2824/// Everything `_entities` needs to resolve a representation to a table row.
2825#[derive(Clone)]
2826struct EntityLookup {
2827    schema_name: String,
2828    table_name: String,
2829    type_name: String,
2830    key_columns: Vec<EntityKeyColumn>,
2831    row_column_types: HashMap<String, String>,
2832}
2833
2834#[derive(Clone)]
2835struct EntityKeyColumn {
2836    field_name: String,
2837    column_name: String,
2838    pg_type: String,
2839    explicit_id: bool,
2840}
2841
2842/// The federation entity resolver's immutable schema view.
2843struct EntityResolverState {
2844    entities: HashMap<String, EntityLookup>,
2845    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2846    names: Arc<crate::names::NameOverrides>,
2847    max_rows: Option<i64>,
2848}
2849
2850impl EntityResolverState {
2851    fn new(
2852        generated: &GeneratedSchema,
2853        relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2854        names: Arc<crate::names::NameOverrides>,
2855        max_rows: Option<i64>,
2856    ) -> Self {
2857        let mut entities = HashMap::new();
2858        for (type_name, entity) in &generated.federation_entities {
2859            let Some(object) = generated.object_types.get(type_name) else {
2860                continue;
2861            };
2862            let mut key_columns = Vec::with_capacity(entity.key_fields.len());
2863            for field_name in &entity.key_fields {
2864                let column_name = names
2865                    .column_source(&object.table.schema, &object.table.name, field_name)
2866                    .unwrap_or(field_name);
2867                let Some(column) = object.table.get_column(column_name) else {
2868                    continue;
2869                };
2870                key_columns.push(EntityKeyColumn {
2871                    field_name: field_name.clone(),
2872                    column_name: column.name.clone(),
2873                    pg_type: column.nominal_type.clone(),
2874                    explicit_id: matches!(
2875                        names.column_type(&object.table.schema, &object.table.name, &column.name),
2876                        Some(crate::types::GraphQLType::Id)
2877                    ),
2878                });
2879            }
2880            if key_columns.len() != entity.key_fields.len() {
2881                continue;
2882            }
2883            entities.insert(
2884                type_name.clone(),
2885                EntityLookup {
2886                    schema_name: object.table.schema.clone(),
2887                    table_name: object.table.name.clone(),
2888                    type_name: type_name.clone(),
2889                    key_columns,
2890                    row_column_types: exposed_column_types(&object.table, names.as_ref()),
2891                },
2892            );
2893        }
2894        Self {
2895            entities,
2896            relationships,
2897            names,
2898            max_rows,
2899        }
2900    }
2901}
2902
2903struct EntityRepresentation {
2904    index: usize,
2905    type_name: String,
2906    values: serde_json::Map<String, serde_json::Value>,
2907}
2908
2909/// Fields selected for one concrete member of the federation `_Entity` union.
2910///
2911/// `SelectionField::selection_set()` expands every fragment but does not apply
2912/// its type condition. Keep a parallel applicability list while walking the
2913/// raw selection tree, then use it to retain only the fields meant for this
2914/// entity type.
2915fn entity_field_applicability(
2916    selection_set: &SelectionSet,
2917    fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2918    type_name: &str,
2919) -> Vec<bool> {
2920    fn condition_matches(condition: Option<&Positioned<TypeCondition>>, type_name: &str) -> bool {
2921        condition.is_none_or(|condition| {
2922            let condition = condition.node.on.node.as_str();
2923            condition == type_name || condition == "_Entity"
2924        })
2925    }
2926
2927    fn collect(
2928        selection_set: &SelectionSet,
2929        fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2930        type_name: &str,
2931        parent_matches: bool,
2932        applicability: &mut Vec<bool>,
2933    ) {
2934        for selection in &selection_set.items {
2935            match &selection.node {
2936                Selection::Field(_) => applicability.push(parent_matches),
2937                Selection::FragmentSpread(spread) => {
2938                    let Some(fragment) = fragments.get(&spread.node.fragment_name.node) else {
2939                        continue;
2940                    };
2941                    collect(
2942                        &fragment.node.selection_set.node,
2943                        fragments,
2944                        type_name,
2945                        parent_matches
2946                            && condition_matches(Some(&fragment.node.type_condition), type_name),
2947                        applicability,
2948                    );
2949                }
2950                Selection::InlineFragment(fragment) => collect(
2951                    &fragment.node.selection_set.node,
2952                    fragments,
2953                    type_name,
2954                    parent_matches
2955                        && condition_matches(fragment.node.type_condition.as_ref(), type_name),
2956                    applicability,
2957                ),
2958            }
2959        }
2960    }
2961
2962    let mut applicability = Vec::new();
2963    collect(
2964        selection_set,
2965        fragments,
2966        type_name,
2967        true,
2968        &mut applicability,
2969    );
2970    applicability
2971}
2972
2973fn entity_selection_fields<'ctx, 'a>(
2974    ctx: &'ctx ResolverContext<'a>,
2975    type_name: &str,
2976) -> Vec<SelectionField<'ctx>> {
2977    let applicability = entity_field_applicability(
2978        &ctx.ctx.item.node.selection_set.node,
2979        &ctx.ctx.query_env.fragments,
2980        type_name,
2981    );
2982    let fields: Vec<_> = ctx.field().selection_set().collect();
2983    debug_assert_eq!(fields.len(), applicability.len());
2984    fields
2985        .into_iter()
2986        .zip(applicability)
2987        .filter_map(|(field, applies)| applies.then_some(field))
2988        .collect()
2989}
2990
2991async fn resolve_entities<'a>(
2992    ctx: &ResolverContext<'a>,
2993    state: &EntityResolverState,
2994) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
2995    let representations = ctx.args.try_get("representations")?.list()?;
2996    let mut grouped: HashMap<String, Vec<EntityRepresentation>> = HashMap::new();
2997    let mut count = 0;
2998    for (index, representation) in representations.iter().enumerate() {
2999        count += 1;
3000        let value = accessor_to_json(&representation);
3001        let serde_json::Value::Object(values) = value else {
3002            return Err(async_graphql::Error::new(
3003                "entity representation must be an object",
3004            ));
3005        };
3006        let type_name = values
3007            .get("__typename")
3008            .and_then(serde_json::Value::as_str)
3009            .ok_or_else(|| {
3010                async_graphql::Error::new("entity representation is missing __typename")
3011            })?
3012            .to_string();
3013        grouped
3014            .entry(type_name.clone())
3015            .or_default()
3016            .push(EntityRepresentation {
3017                index,
3018                type_name,
3019                values,
3020            });
3021    }
3022
3023    let mut resolved: Vec<Option<FieldValue<'a>>> =
3024        std::iter::repeat_with(|| None).take(count).collect();
3025    for (type_name, representations) in grouped {
3026        let lookup = state.entities.get(&type_name).ok_or_else(|| {
3027            async_graphql::Error::new(format!("unknown federation entity \"{}\"", type_name))
3028        })?;
3029        let rows = resolve_entity_group(ctx, lookup, &representations, state).await?;
3030        for (ordinality, representation) in representations.into_iter().enumerate() {
3031            resolved[representation.index] = Some(match &rows[ordinality] {
3032                Some(row) => FieldValue::value(json_to_value(row.clone()))
3033                    .with_type(representation.type_name),
3034                None => FieldValue::NULL,
3035            });
3036        }
3037    }
3038
3039    Ok(Some(FieldValue::list(resolved.into_iter().map(|value| {
3040        value.expect("every representation was resolved or errored")
3041    }))))
3042}
3043
3044async fn resolve_entity_group(
3045    ctx: &ResolverContext<'_>,
3046    lookup: &EntityLookup,
3047    representations: &[EntityRepresentation],
3048    state: &EntityResolverState,
3049) -> Result<Vec<Option<serde_json::Value>>, async_graphql::Error> {
3050    if representations.is_empty() {
3051        return Ok(Vec::new());
3052    }
3053
3054    let pool = ctx.data::<PgPool>()?;
3055    let gql_ctx = ctx.data::<GraphQLContext>()?;
3056    let mut bound_values = Vec::new();
3057    let mut value_rows = Vec::with_capacity(representations.len());
3058    for (ordinality, representation) in representations.iter().enumerate() {
3059        let normalized_values = normalized_entity_key_values(
3060            &representation.values,
3061            &lookup.key_columns,
3062            &lookup.type_name,
3063        )?;
3064        let mut values = Vec::with_capacity(lookup.key_columns.len() + 1);
3065        for (key_column, value) in lookup.key_columns.iter().zip(normalized_values) {
3066            values.push(format!(
3067                "${}::{}",
3068                bound_values.len() + 1,
3069                key_column.pg_type
3070            ));
3071            bound_values.push(value);
3072        }
3073        values.push(ordinality.to_string());
3074        value_rows.push(format!("({})", values.join(", ")));
3075    }
3076
3077    let representation_alias = "pgrst_entity_representation";
3078    let ordinality_column = "pgrst_entity_ordinality";
3079    let key_aliases: Vec<String> = (0..lookup.key_columns.len())
3080        .map(|index| format!("pgrst_entity_key_{index}"))
3081        .collect();
3082    let mut representation_columns: Vec<String> = key_aliases
3083        .iter()
3084        .map(|column| postrust_sql::escape_ident(column))
3085        .collect();
3086    representation_columns.push(postrust_sql::escape_ident(ordinality_column));
3087    let representations_sql = format!(
3088        "(VALUES {}) AS {} ({})",
3089        value_rows.join(", "),
3090        postrust_sql::escape_ident(representation_alias),
3091        representation_columns.join(", ")
3092    );
3093    let key_predicate = lookup
3094        .key_columns
3095        .iter()
3096        .zip(&key_aliases)
3097        .map(|(key_column, key_alias)| {
3098            format!(
3099                "{}.{} = {}.{}",
3100                postrust_sql::escape_ident(READ_ROW),
3101                postrust_sql::escape_ident(&key_column.column_name),
3102                postrust_sql::escape_ident(representation_alias),
3103                postrust_sql::escape_ident(key_alias)
3104            )
3105        })
3106        .collect::<Vec<_>>()
3107        .join(" AND ");
3108    let mut where_sql = format!(" WHERE ({key_predicate})");
3109
3110    let permission = permission_predicate(
3111        &gql_ctx.caller(),
3112        state.names.as_ref(),
3113        &lookup.schema_name,
3114        &lookup.table_name,
3115    )?;
3116    if let Some(predicate) = permission {
3117        let guard = gql_ctx
3118            .schema_cache
3119            .get()
3120            .await
3121            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3122        let cache = guard
3123            .as_ref()
3124            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3125        let scope = WhereScope::table(
3126            &lookup.schema_name,
3127            &lookup.table_name,
3128            &lookup.type_name,
3129            state.names.as_ref(),
3130        )
3131        .under_alias(READ_ROW)
3132        .with_resolution(cache, state.relationships.as_ref())
3133        .for_caller(gql_ctx.caller());
3134        let (filter_sql, filter_values) =
3135            build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3136        if !filter_sql.is_empty() {
3137            where_sql = format!(
3138                "{} AND ({})",
3139                where_sql,
3140                filter_sql.trim_start_matches("WHERE ")
3141            );
3142            bound_values.extend(filter_values);
3143        }
3144    }
3145
3146    let source = format!(
3147        "{}.{} AS {}",
3148        postrust_sql::escape_ident(&lookup.schema_name),
3149        postrust_sql::escape_ident(&lookup.table_name),
3150        postrust_sql::escape_ident(READ_ROW)
3151    );
3152    let source_rows = format!(
3153        "SELECT {}.* FROM {}{}",
3154        postrust_sql::escape_ident(READ_ROW),
3155        source,
3156        where_sql
3157    );
3158    let mut projection = {
3159        let guard = gql_ctx
3160            .schema_cache
3161            .get()
3162            .await
3163            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3164        guard
3165            .as_ref()
3166            .and_then(|cache| {
3167                cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3168                    &lookup.schema_name,
3169                    &lookup.table_name,
3170                ))
3171            })
3172            .and_then(|table| rename_projection(table, "src", state.names.as_ref()))
3173            .unwrap_or_else(|| "src.*".to_string())
3174    };
3175    let projected_rows = {
3176        let guard = gql_ctx
3177            .schema_cache
3178            .get()
3179            .await
3180            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3181        let cache = guard
3182            .as_ref()
3183            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3184        let table = cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3185            &lookup.schema_name,
3186            &lookup.table_name,
3187        ));
3188        let selected_fields = entity_selection_fields(ctx, &lookup.type_name);
3189        let mut param_idx = bound_values.len() + 1;
3190        let computed = match table {
3191            Some(table) => computed_projections(
3192                table,
3193                selected_fields.iter().copied(),
3194                "src",
3195                state.names.as_ref(),
3196                cache,
3197                &mut param_idx,
3198                &mut bound_values,
3199            )?,
3200            None => Vec::new(),
3201        };
3202        let embeds = build_embed_expressions(
3203            &gql_ctx.caller(),
3204            cache,
3205            state.relationships.as_ref(),
3206            &lookup.type_name,
3207            "src",
3208            selected_fields,
3209            state.max_rows,
3210            &mut 0,
3211            &mut param_idx,
3212            &mut bound_values,
3213            state.names.as_ref(),
3214        )?;
3215        for expression in &computed {
3216            projection.push_str(", ");
3217            projection.push_str(expression);
3218        }
3219        for (field_name, expression) in &embeds {
3220            projection.push_str(", ");
3221            projection.push_str(expression);
3222            projection.push_str(" AS ");
3223            projection.push_str(&postrust_sql::escape_ident(field_name));
3224        }
3225        format!("SELECT {} FROM ({}) AS src", projection, source_rows)
3226    };
3227    // PostgreSQL decides whether an entity key matches. Carry the input's
3228    // ordinal through that same query instead of reconstructing a key from
3229    // row JSON: PostgreSQL-equivalent spellings (UUID case, citext case and
3230    // timestamptz offsets, for example) need to resolve to the row it found.
3231    let sql = format!(
3232        "SELECT json_build_array({}.{}, {}) FROM {} CROSS JOIN LATERAL ({}) AS t",
3233        postrust_sql::escape_ident(representation_alias),
3234        postrust_sql::escape_ident(ordinality_column),
3235        row_json("t", &lookup.row_column_types),
3236        representations_sql,
3237        projected_rows,
3238    );
3239    let mut tx = begin_with_session(pool, gql_ctx).await?;
3240    let rows = execute_query_on(&mut tx, &sql, &bound_values).await?;
3241    tx.commit().await?;
3242
3243    let mut by_ordinal = std::iter::repeat_with(|| None)
3244        .take(representations.len())
3245        .collect::<Vec<_>>();
3246    for result in rows {
3247        let serde_json::Value::Array(mut pair) = result else {
3248            return Err(async_graphql::Error::new(
3249                "entity query returned an invalid result",
3250            ));
3251        };
3252        if pair.len() != 2 {
3253            return Err(async_graphql::Error::new(
3254                "entity query returned an invalid result",
3255            ));
3256        }
3257        let row = pair.pop().expect("the result pair has two values");
3258        let ordinality = pair
3259            .pop()
3260            .and_then(|value| value.as_u64())
3261            .and_then(|value| usize::try_from(value).ok())
3262            .filter(|value| *value < by_ordinal.len())
3263            .ok_or_else(|| async_graphql::Error::new("entity query returned an invalid ordinal"))?;
3264        by_ordinal[ordinality] = Some(row);
3265    }
3266    Ok(by_ordinal)
3267}
3268
3269/// Validate an entity key and normalize explicit GraphQL `ID` overrides to
3270/// their integer, text or UUID storage type. `_Any` deliberately skips the
3271/// field-level checks ordinary GraphQL arguments receive.
3272fn normalized_entity_key_values(
3273    values: &serde_json::Map<String, serde_json::Value>,
3274    key_columns: &[EntityKeyColumn],
3275    type_name: &str,
3276) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
3277    let mut key = Vec::with_capacity(key_columns.len());
3278    for key_column in key_columns {
3279        let value = values.get(&key_column.field_name).ok_or_else(|| {
3280            async_graphql::Error::new(format!(
3281                "entity representation for \"{}\" is missing key field \"{}\"",
3282                type_name, key_column.field_name
3283            ))
3284        })?;
3285        key.push(normalize_entity_key_value(type_name, key_column, value)?);
3286    }
3287    Ok(key)
3288}
3289
3290fn normalize_entity_key_value(
3291    type_name: &str,
3292    key_column: &EntityKeyColumn,
3293    value: &serde_json::Value,
3294) -> Result<serde_json::Value, async_graphql::Error> {
3295    let invalid = |expected: &str| {
3296        async_graphql::Error::new(format!(
3297            "entity representation for \"{}\" has invalid key field \"{}\": expected {}, found {}",
3298            type_name,
3299            key_column.field_name,
3300            expected,
3301            json_value_kind(value)
3302        ))
3303    };
3304
3305    if key_column.explicit_id {
3306        return normalize_graphql_id(value, &key_column.pg_type).ok_or_else(|| {
3307            invalid(&format!(
3308                "an ID compatible with PostgreSQL type \"{}\"",
3309                key_column.pg_type
3310            ))
3311        });
3312    }
3313
3314    let pg_type = key_column.pg_type.trim().to_ascii_lowercase();
3315    let normalized = if matches!(pg_type.as_str(), "int8" | "bigint") {
3316        match value {
3317            serde_json::Value::Number(number) => number.as_i64(),
3318            serde_json::Value::String(text) => text.parse::<i64>().ok(),
3319            _ => None,
3320        }
3321        .map(serde_json::Value::from)
3322    } else if integer_bounds(&pg_type).is_some() {
3323        value.as_i64().and_then(|integer| {
3324            let (min, max) = integer_bounds(&pg_type)?;
3325            (min..=max)
3326                .contains(&integer)
3327                .then(|| serde_json::Value::from(integer))
3328        })
3329    } else if matches!(
3330        pg_type.as_str(),
3331        "float4" | "float8" | "real" | "double precision"
3332    ) {
3333        value
3334            .as_f64()
3335            .and_then(serde_json::Number::from_f64)
3336            .map(serde_json::Value::Number)
3337    } else if matches!(pg_type.as_str(), "numeric" | "decimal") {
3338        match value {
3339            serde_json::Value::Number(number) => {
3340                Some(serde_json::Value::String(number.to_string()))
3341            }
3342            serde_json::Value::String(text) if text.parse::<sqlx::types::BigDecimal>().is_ok() => {
3343                Some(serde_json::Value::String(text.clone()))
3344            }
3345            _ => None,
3346        }
3347    } else if matches!(pg_type.as_str(), "bool" | "boolean") {
3348        value.as_bool().map(serde_json::Value::Bool)
3349    } else if matches!(pg_type.as_str(), "json" | "jsonb") {
3350        Some(value.clone())
3351    } else if is_array_type(&pg_type) {
3352        value.is_array().then(|| value.clone())
3353    } else {
3354        value
3355            .as_str()
3356            .map(|text| serde_json::Value::String(text.to_string()))
3357    };
3358
3359    normalized.ok_or_else(|| {
3360        invalid(&format!(
3361            "a value compatible with PostgreSQL type \"{}\"",
3362            key_column.pg_type
3363        ))
3364    })
3365}
3366
3367fn normalize_graphql_id(value: &serde_json::Value, pg_type: &str) -> Option<serde_json::Value> {
3368    if integer_bounds(pg_type).is_some() {
3369        let integer = match value {
3370            serde_json::Value::Number(number) => number.as_i64(),
3371            serde_json::Value::String(text) => text.parse::<i64>().ok(),
3372            _ => None,
3373        }?;
3374        let (min, max) = integer_bounds(pg_type)?;
3375        return (min..=max)
3376            .contains(&integer)
3377            .then(|| serde_json::Value::from(integer));
3378    }
3379    if pg_type.eq_ignore_ascii_case("uuid") {
3380        return normalize_uuid(value).map(serde_json::Value::String);
3381    }
3382    if is_text_type(pg_type) {
3383        return match value {
3384            serde_json::Value::String(s) => Some(serde_json::Value::String(s.clone())),
3385            serde_json::Value::Number(n) if n.is_i64() || n.is_u64() => {
3386                Some(serde_json::Value::String(n.to_string()))
3387            }
3388            _ => None,
3389        };
3390    }
3391    None
3392}
3393
3394fn integer_bounds(pg_type: &str) -> Option<(i64, i64)> {
3395    match pg_type.trim().to_ascii_lowercase().as_str() {
3396        "int2" | "smallint" => Some((i16::MIN.into(), i16::MAX.into())),
3397        "int4" | "int" | "integer" => Some((i32::MIN.into(), i32::MAX.into())),
3398        "int8" | "bigint" => Some((i64::MIN, i64::MAX)),
3399        _ => None,
3400    }
3401}
3402
3403fn is_text_type(pg_type: &str) -> bool {
3404    matches!(
3405        pg_type.trim().to_ascii_lowercase().as_str(),
3406        "text" | "varchar" | "character varying" | "char" | "character" | "bpchar" | "citext"
3407    )
3408}
3409
3410fn normalize_uuid(value: &serde_json::Value) -> Option<String> {
3411    sqlx::types::Uuid::parse_str(value.as_str()?)
3412        .ok()
3413        .map(|uuid| uuid.to_string())
3414}
3415
3416fn json_value_kind(value: &serde_json::Value) -> &'static str {
3417    match value {
3418        serde_json::Value::Null => "null",
3419        serde_json::Value::Bool(_) => "a boolean",
3420        serde_json::Value::Number(_) => "a number",
3421        serde_json::Value::String(_) => "a string",
3422        serde_json::Value::Array(_) => "an array",
3423        serde_json::Value::Object(_) => "an object",
3424    }
3425}
3426
3427/// Add the root fields for functions that answer with rows of a table.
3428///
3429/// The same arguments a table's own root field takes, because the rows are the
3430/// same rows: what the function adds is `args`, which is where its own
3431/// arguments go so that a parameter called `limit` cannot shadow the one that
3432/// pages the result.
3433fn add_function_fields(
3434    mut root: Object,
3435    generated: &GeneratedSchema,
3436    volatile: bool,
3437    max_rows: Option<i64>,
3438    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3439    names: Arc<crate::names::NameOverrides>,
3440) -> Object {
3441    // The names the root already carries, from the tables. A function may be
3442    // named after one -- `create function t() returns setof t` is an ordinary
3443    // way to write a parameterised view -- and `Object::field` *panics* on a
3444    // duplicate, so an unguarded collision is a server that will not start.
3445    let taken: HashSet<&str> = if volatile {
3446        generated
3447            .mutation_fields
3448            .iter()
3449            .map(|f| f.name.as_str())
3450            .collect()
3451    } else {
3452        generated
3453            .query_fields
3454            .iter()
3455            .map(|f| f.name.as_str())
3456            .collect()
3457    };
3458
3459    for function in &generated.function_fields {
3460        if function.volatile != volatile {
3461            continue;
3462        }
3463        if taken.contains(function.name.as_str()) {
3464            tracing::warn!(
3465                "not exposing {}.{}({}) -- a table already has a root field \
3466                 called {}",
3467                function.schema_name,
3468                function.function_name,
3469                function.argument_signature(),
3470                function.name,
3471            );
3472            continue;
3473        }
3474        let spec = Arc::new(QueryFieldSpec {
3475            schema_name: function.returns_table.0.clone(),
3476            table_name: function.returns_table.1.clone(),
3477            type_name: function.returns.clone(),
3478            is_by_pk: false,
3479            pk_columns: Vec::new(),
3480            max_rows,
3481            relationships: Arc::clone(&relationships),
3482            names: Arc::clone(&names),
3483            call: Some(Arc::new(FunctionCall {
3484                schema: function.schema_name.clone(),
3485                name: function.function_name.clone(),
3486                arguments: function.arguments.clone(),
3487                session_argument: function.session_argument.clone(),
3488            })),
3489        });
3490
3491        let mut field = Field::new(
3492            &function.name,
3493            TypeRef::named_nn_list_nn(&function.returns),
3494            move |ctx| {
3495                let spec = Arc::clone(&spec);
3496                FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
3497            },
3498        );
3499        if !function.arguments.is_empty() {
3500            field = field.argument(InputValue::new(
3501                "args",
3502                TypeRef::named_nn(format!("{}_args", function.name)),
3503            ));
3504        }
3505        field = field
3506            .argument(InputValue::new(
3507                "where",
3508                TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
3509                    &function.local_returns,
3510                )),
3511            ))
3512            .argument(InputValue::new(
3513                "order_by",
3514                TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
3515                    &function.local_returns,
3516                )),
3517            ))
3518            .argument(InputValue::new(
3519                "distinct_on",
3520                TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
3521                    &function.local_returns,
3522                )),
3523            ))
3524            .argument(InputValue::new("limit", TypeRef::named("Int")))
3525            .argument(InputValue::new("offset", TypeRef::named("Int")));
3526        if let Some(description) = &function.description {
3527            field = field.description(description);
3528        }
3529        root = root.field(field);
3530
3531        // The same rows, with numbers about them. Every table root has one and
3532        // so does every function root: `search_tracks_aggregate(args: {…})`
3533        // counts what `search_tracks(args: {…})` would have answered with,
3534        // which means calling the function and taking the same arguments.
3535        let agg_spec = Arc::new(AggregateSpec {
3536            schema_name: function.returns_table.0.clone(),
3537            table_name: function.returns_table.1.clone(),
3538            type_name: function.returns.clone(),
3539            max_rows,
3540            relationships: Arc::clone(&relationships),
3541            names: Arc::clone(&names),
3542            call: Some(Arc::new(FunctionCall {
3543                schema: function.schema_name.clone(),
3544                name: function.function_name.clone(),
3545                arguments: function.arguments.clone(),
3546                session_argument: function.session_argument.clone(),
3547            })),
3548        });
3549        let mut aggregate = Field::new(
3550            format!("{}_aggregate", function.name),
3551            TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
3552                &function.local_returns,
3553            )),
3554            move |ctx| {
3555                let agg_spec = Arc::clone(&agg_spec);
3556                FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
3557            },
3558        );
3559        if !function.arguments.is_empty() {
3560            aggregate = aggregate.argument(InputValue::new(
3561                "args",
3562                TypeRef::named_nn(format!("{}_args", function.name)),
3563            ));
3564        }
3565        aggregate = with_row_arguments(aggregate, &function.local_returns).description(format!(
3566            "fetch aggregated fields from the table: \"{}\"",
3567            function.returns_table.1
3568        ));
3569        root = root.field(aggregate);
3570    }
3571    root
3572}
3573
3574/// The FROM entry for a root field that calls a function.
3575///
3576/// Aliased as the table the function answers with rows of, so a filter or an
3577/// ordering written against that table's columns finds them: without it the
3578/// FROM clause names the function and `article.score` is a table nobody
3579/// mentioned. Whatever the caller put under `args` is bound here, which is why
3580/// it takes the parameter list.
3581fn function_source(
3582    ctx: &ResolverContext<'_>,
3583    call: &FunctionCall,
3584    alias: &str,
3585    bound_values: &mut Vec<serde_json::Value>,
3586) -> Result<String, async_graphql::Error> {
3587    let given = ctx
3588        .args
3589        .try_get("args")
3590        .ok()
3591        .map(|v| accessor_to_json(&v))
3592        .unwrap_or_else(|| serde_json::json!({}));
3593    let mut passed = Vec::new();
3594    for (name, _, required) in &call.arguments {
3595        match given.get(name) {
3596            None if *required => {
3597                return Err(async_graphql::Error::new(format!(
3598                    "{} needs the argument \"{}\"",
3599                    call.name, name
3600                )))
3601            }
3602            // An argument left out is left out, so the function's own default
3603            // applies. Passing null instead would override it.
3604            None => continue,
3605            Some(value) => {
3606                // Named notation, so the arguments a client did give land on
3607                // the parameters it meant regardless of order. A GraphQL Int
3608                // binds as a bigint, and `f(article_id => bigint)` is not
3609                // `f(integer)` -- the function is looked up by the types of
3610                // what it was handed, so what it was handed has to be the
3611                // types it declares.
3612                let (_, pg_type, _) = call
3613                    .arguments
3614                    .iter()
3615                    .find(|(argument, _, _)| argument == name)
3616                    .expect("iterating the arguments");
3617                passed.push(format!(
3618                    "{} => ${}::{}",
3619                    postrust_sql::escape_ident(name),
3620                    bound_values.len() + 1,
3621                    pg_type
3622                ));
3623                bound_values.push(value.clone());
3624            }
3625        }
3626    }
3627    // The session argument is not the client's to send: it says who is
3628    // asking, and a caller that could write it could name any identity it
3629    // liked. It is filled here, from the verified token.
3630    if let Some(session) = &call.session_argument {
3631        passed.push(format!(
3632            "{} => {}",
3633            postrust_sql::escape_ident(session),
3634            SESSION_ARGUMENT
3635        ));
3636    }
3637    Ok(format!(
3638        "{}.{}({}) AS {}",
3639        postrust_sql::escape_ident(&call.schema),
3640        postrust_sql::escape_ident(&call.name),
3641        passed.join(", "),
3642        postrust_sql::escape_ident(alias)
3643    ))
3644}
3645
3646/// A function a root field calls instead of reading a table.
3647struct FunctionCall {
3648    schema: String,
3649    name: String,
3650    arguments: Vec<(String, String, bool)>,
3651    /// The parameter filled from the caller's session rather than from the
3652    /// request. See [`crate::schema::FunctionField::session_argument`].
3653    session_argument: Option<String>,
3654}
3655
3656/// Everything an aggregate field's resolver needs.
3657struct AggregateSpec {
3658    schema_name: String,
3659    table_name: String,
3660    type_name: String,
3661    max_rows: Option<i64>,
3662    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3663    names: Arc<crate::names::NameOverrides>,
3664    /// Set where the rows come from a function rather than from the table
3665    /// itself, exactly as it is on [`QueryFieldSpec`]. Counting the rows a
3666    /// function answers with means calling it.
3667    call: Option<Arc<FunctionCall>>,
3668}
3669
3670/// Resolve an aggregate field.
3671///
3672/// One pass over the selection decides what SQL to write: a client asking only
3673/// for `count` should not pay for the rows, and one asking only for `nodes`
3674/// should not pay for a second scan. Both halves read from the same filtered
3675/// subquery, so `aggregate` describes exactly the set `nodes` came from.
3676async fn resolve_aggregate<'a>(
3677    ctx: &ResolverContext<'a>,
3678    spec: &AggregateSpec,
3679) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3680    Ok(Some(FieldValue::value(aggregate_value(ctx, spec).await?)))
3681}
3682
3683/// The numbers an aggregate field answers with.
3684///
3685/// Split from the resolver for the reason [`query_rows`] is: a subscription
3686/// reads them over and over and has to tell one answer from the last.
3687async fn aggregate_value(
3688    ctx: &ResolverContext<'_>,
3689    spec: &AggregateSpec,
3690) -> Result<Value, async_graphql::Error> {
3691    use crate::schema::aggregate as agg;
3692
3693    let pool = ctx.data::<PgPool>()?;
3694    let gql_ctx = ctx.data::<GraphQLContext>()?;
3695
3696    let mut bound_values: Vec<serde_json::Value> = Vec::new();
3697    // A function's own arguments are bound before anything else, so a
3698    // predicate written beside them continues the numbering rather than
3699    // colliding with it.
3700    let source = match &spec.call {
3701        None => format!(
3702            "{}.{}",
3703            postrust_sql::escape_ident(&spec.schema_name),
3704            postrust_sql::escape_ident(&spec.table_name)
3705        ),
3706        Some(call) => function_source(ctx, call, &spec.table_name, &mut bound_values)?,
3707    };
3708
3709    let mut where_sql = String::new();
3710    {
3711        // Counting rows is reading them, so the permission applies here in the
3712        // same breath as the request's own filter -- otherwise `count` would
3713        // report a total the same role cannot list.
3714        let requested = ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v));
3715        let permission = permission_predicate(
3716            &gql_ctx.caller(),
3717            spec.names.as_ref(),
3718            &spec.schema_name,
3719            &spec.table_name,
3720        )?;
3721        if let Some(predicate) = and_predicates(requested, permission) {
3722            let guard = gql_ctx
3723                .schema_cache
3724                .get()
3725                .await
3726                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3727            let cache = guard
3728                .as_ref()
3729                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3730            // A function's rows are read under the table's name as an alias,
3731            // not from the table itself -- the same split the row read makes.
3732            // Counting them under the qualified name answered `invalid
3733            // reference to FROM-clause entry for table "Track"` the moment a
3734            // permission referred to a column, which is any permission that
3735            // follows a relationship.
3736            let scope = match &spec.call {
3737                None => WhereScope::table(
3738                    &spec.schema_name,
3739                    &spec.table_name,
3740                    &spec.type_name,
3741                    spec.names.as_ref(),
3742                )
3743                .with_resolution(cache, spec.relationships.as_ref()),
3744                Some(_) => WhereScope::for_alias(
3745                    &spec.schema_name,
3746                    &spec.table_name,
3747                    &spec.table_name,
3748                    &spec.type_name,
3749                    cache,
3750                    spec.relationships.as_ref(),
3751                    spec.names.as_ref(),
3752                ),
3753            }
3754            .for_caller(gql_ctx.caller());
3755            let (sql, values) =
3756                build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3757            if !sql.is_empty() {
3758                where_sql = format!(" {}", sql);
3759                bound_values.extend(values);
3760            }
3761        }
3762    }
3763
3764    let order_sql = build_order_by_clause(
3765        ctx,
3766        &gql_ctx.schema_cache,
3767        &spec.schema_name,
3768        &spec.table_name,
3769        &spec.type_name,
3770        spec.relationships.as_ref(),
3771        &format!(
3772            "{}.{}",
3773            postrust_sql::escape_ident(&spec.schema_name),
3774            postrust_sql::escape_ident(&spec.table_name)
3775        ),
3776        spec.names.as_ref(),
3777    )
3778    .await?;
3779
3780    let requested_limit = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
3781    let offset = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
3782    let ceiling = match (
3783        spec.max_rows,
3784        permission_limit(
3785            &gql_ctx.caller(),
3786            spec.names.as_ref(),
3787            &spec.schema_name,
3788            &spec.table_name,
3789        ),
3790    ) {
3791        (Some(configured), Some(granted)) => Some(configured.min(granted)),
3792        (only, None) | (None, only) => only,
3793    };
3794    let limit = match (requested_limit, ceiling) {
3795        (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
3796        (Some(requested), None) => Some(requested),
3797        (None, ceiling) => ceiling,
3798    };
3799
3800    let rows = format!("SELECT * FROM {}{}{}", source, where_sql, order_sql);
3801
3802    // What `nodes` reads: the page, bounded by whichever ceiling is lowest.
3803    let mut inner = rows.clone();
3804    if let Some(limit) = limit {
3805        inner.push_str(&format!(" LIMIT {}", limit));
3806    }
3807    if let Some(offset) = offset {
3808        inner.push_str(&format!(" OFFSET {}", offset));
3809    }
3810
3811    // What `aggregate` reads, which is not the same rows. A count is not a
3812    // page: a ceiling exists to bound how many rows travel, and answering
3813    // "how many are there" with "as many as I would have sent you" is not an
3814    // answer to the question. Hasura says so too -- a role limited to one row
3815    // of `article` still counts three of them and still gets `max(id)` over
3816    // all three, which is what `agg_perm` tests.
3817    //
3818    // The request's own `limit` and `offset` do still apply: those were asked
3819    // for, and `article_aggregate(limit: 2)` is a question about two rows. The
3820    // corpus proves this only for the permission's ceiling; the configured one
3821    // is treated the same way because the reason is the same, and because a
3822    // server whose `PGRST_MAX_ROWS` silently became the answer to `count`
3823    // would be wrong in the way that is hardest to notice.
3824    let mut counted = rows;
3825    if let Some(limit) = requested_limit {
3826        counted.push_str(&format!(" LIMIT {}", limit));
3827    }
3828    if let Some(offset) = offset {
3829        counted.push_str(&format!(" OFFSET {}", offset));
3830    }
3831
3832    // What the client actually asked for. Two selections of one aggregate are
3833    // one entry: `sum { id }` beside `totals: sum { views }` reads the same
3834    // object under two names, so the columns are the union of what both named
3835    // and the object is built once.
3836    let mut wants_nodes = false;
3837    let mut wants_aggregate = false;
3838    let mut wanted: BTreeMap<String, Vec<String>> = BTreeMap::new();
3839    // `count` is different: each occurrence may count something else, so each
3840    // is answered under the name it was asked for.
3841    let mut counts: Vec<(String, String)> = Vec::new();
3842    for selection in ctx.field().selection_set() {
3843        match selection.name() {
3844            "nodes" => wants_nodes = true,
3845            "aggregate" => {
3846                wants_aggregate = true;
3847                for function in selection.selection_set() {
3848                    // Answered by the schema, at this level and the one below.
3849                    if function.name() == "__typename" {
3850                        continue;
3851                    }
3852                    if function.name() == "count" {
3853                        counts.push((
3854                            function.alias().unwrap_or("count").to_string(),
3855                            count_expression(
3856                                function,
3857                                (&spec.schema_name, &spec.table_name),
3858                                None,
3859                                spec.names.as_ref(),
3860                            ),
3861                        ));
3862                        continue;
3863                    }
3864                    // Entered even when it names no column: `max { __typename }`
3865                    // is an object with nothing in it, not a null.
3866                    let columns = wanted.entry(function.name().to_string()).or_default();
3867                    for column in function.selection_set() {
3868                        let name = column.name().to_string();
3869                        if name != "__typename" && !columns.contains(&name) {
3870                            columns.push(name);
3871                        }
3872                    }
3873                }
3874            }
3875            _ => {}
3876        }
3877    }
3878
3879    let mut result = async_graphql::indexmap::IndexMap::new();
3880
3881    // Whatever `aggregate` names -- even if that is only `__typename` -- it is
3882    // an object, so it is answered rather than left null.
3883    if wants_aggregate {
3884        // `count` is answered whether or not it was asked for: the aggregate
3885        // type's own resolver reads a missing key as zero, and a client that
3886        // asked for nothing else still gets a number rather than a null.
3887        let mut parts = match counts.iter().any(|(key, _)| key == "count") {
3888            true => Vec::new(),
3889            false => vec!["'count', count(*)".to_string()],
3890        };
3891        for (key, expression) in &counts {
3892            parts.push(format!(
3893                "{}, {}",
3894                postrust_sql::quote_literal(key),
3895                expression
3896            ));
3897        }
3898        for (function, columns) in &wanted {
3899            // The function name comes from the schema, not from the request:
3900            // a selection can only name a field that was generated, so there
3901            // is no path from client text to this identifier.
3902            let sql_function = agg::NUMERIC_AGGREGATES
3903                .iter()
3904                .chain(agg::ORDERED_AGGREGATES.iter())
3905                .find(|(name, _)| name == function)
3906                .map(|(name, _)| *name)
3907                .ok_or_else(|| {
3908                    async_graphql::Error::new(format!("unknown aggregate \"{}\"", function))
3909                })?;
3910            let per_column: Vec<String> = columns
3911                .iter()
3912                .map(|field| {
3913                    let source = spec
3914                        .names
3915                        .column_source(&spec.schema_name, &spec.table_name, field)
3916                        .unwrap_or(field);
3917                    format!(
3918                        "{}, {}({})",
3919                        postrust_sql::quote_literal(field),
3920                        sql_function,
3921                        postrust_sql::escape_ident(source)
3922                    )
3923                })
3924                .collect();
3925            parts.push(format!(
3926                "'{}', json_build_object({})",
3927                sql_function,
3928                per_column.join(", ")
3929            ));
3930        }
3931
3932        let sql = format!(
3933            "SELECT json_build_object({}) FROM ({}) AS pgrst_agg",
3934            parts.join(", "),
3935            counted
3936        );
3937        let mut conn = begin_with_session(pool, gql_ctx).await?;
3938        let rows = execute_query_on(&mut conn, &sql, &bound_values).await?;
3939        conn.commit().await?;
3940        if let Some(first) = rows.into_iter().next() {
3941            if let Value::Object(map) = json_to_value(first) {
3942                result.insert(async_graphql::Name::new("aggregate"), Value::Object(map));
3943            }
3944        }
3945    }
3946
3947    if wants_nodes {
3948        // `nodes` is the same rows selection the plain root field answers, so
3949        // it gets the same projection: computed fields, which are functions of
3950        // the row rather than part of it, and relationships, which are
3951        // correlated subselects. Reading it as columns alone was why a
3952        // relationship asked for beside a count came back null.
3953        let nodes = ctx
3954            .field()
3955            .selection_set()
3956            .find(|selection| selection.name() == "nodes")
3957            .ok_or_else(|| async_graphql::Error::new("nodes was asked for and then was not"))?;
3958
3959        // The aggregate query has already been sent, so its parameters are
3960        // fixed; anything an embed binds continues that numbering in a vector
3961        // of its own.
3962        let mut node_values = bound_values.clone();
3963        let (projection, row_column_types) = {
3964            let guard = gql_ctx
3965                .schema_cache
3966                .get()
3967                .await
3968                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3969            let cache = guard
3970                .as_ref()
3971                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3972            let qi = postrust_core::api_request::QualifiedIdentifier::new(
3973                &spec.schema_name,
3974                &spec.table_name,
3975            );
3976            let table = cache.get_table(&qi);
3977            let mut param_idx = node_values.len() + 1;
3978            let computed = match table {
3979                Some(table) => computed_projections(
3980                    table,
3981                    nodes.selection_set(),
3982                    "src",
3983                    spec.names.as_ref(),
3984                    cache,
3985                    &mut param_idx,
3986                    &mut node_values,
3987                )?,
3988                None => Vec::new(),
3989            };
3990            let embeds = build_embed_expressions(
3991                &gql_ctx.caller(),
3992                cache,
3993                spec.relationships.as_ref(),
3994                &spec.type_name,
3995                "src",
3996                nodes.selection_set(),
3997                spec.max_rows,
3998                &mut 0,
3999                &mut param_idx,
4000                &mut node_values,
4001                spec.names.as_ref(),
4002            )?;
4003            // A renamed column is renamed here, in the projection over the
4004            // subquery -- not inside it, where `src` has to stay the table's
4005            // own composite for a computed field to be passed the row.
4006            let mut projection = table
4007                .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4008                .unwrap_or_else(|| "src.*".to_string());
4009            for expression in &computed {
4010                projection.push_str(", ");
4011                projection.push_str(expression);
4012            }
4013            for (field_name, expression) in &embeds {
4014                projection.push_str(", ");
4015                projection.push_str(expression);
4016                projection.push_str(" AS ");
4017                projection.push_str(&postrust_sql::escape_ident(field_name));
4018            }
4019            (
4020                projection,
4021                table
4022                    .map(|table| exposed_column_types(table, spec.names.as_ref()))
4023                    .unwrap_or_default(),
4024            )
4025        };
4026
4027        let sql = format!(
4028            "SELECT {} FROM (SELECT {} FROM ({}) AS src) AS pgrst_nodes",
4029            row_json("pgrst_nodes", &row_column_types),
4030            projection,
4031            inner
4032        );
4033        let mut conn = begin_with_session(pool, gql_ctx).await?;
4034        let rows = execute_query_on(&mut conn, &sql, &node_values).await?;
4035        conn.commit().await?;
4036        result.insert(
4037            async_graphql::Name::new("nodes"),
4038            Value::List(rows.into_iter().map(json_to_value).collect()),
4039        );
4040    }
4041
4042    let _ = &spec.type_name;
4043    Ok(Value::Object(result))
4044}
4045
4046/// Resolve a query field.
4047async fn resolve_query<'a>(
4048    ctx: &ResolverContext<'a>,
4049    spec: &QueryFieldSpec,
4050) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4051    Ok(rows_as_field_value(
4052        query_rows(ctx, spec).await?,
4053        spec.is_by_pk,
4054    ))
4055}
4056
4057/// The rows a query field answers with, as JSON.
4058///
4059/// Split from the field's own resolver because a subscription reads the same
4060/// rows over and over and has to tell one answer from the last: a
4061/// `FieldValue` is neither comparable nor readable, and this is.
4062async fn query_rows(
4063    ctx: &ResolverContext<'_>,
4064    spec: &QueryFieldSpec,
4065) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4066    let schema_name = spec.schema_name.as_str();
4067    let table_name = spec.table_name.as_str();
4068    let type_name = spec.type_name.as_str();
4069    let is_by_pk = spec.is_by_pk;
4070    let pk_columns = spec.pk_columns.as_slice();
4071    let max_rows = spec.max_rows;
4072    let relationships = spec.relationships.as_ref();
4073
4074    let pool = ctx.data::<PgPool>()?;
4075    let gql_ctx = ctx.data::<GraphQLContext>()?;
4076
4077    debug!("Resolving query for table: {}", table_name);
4078
4079    // Extract pagination arguments
4080    let requested_limit: Option<i64> = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
4081
4082    let offset: Option<i64> = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
4083
4084    // A query that names no limit would otherwise select the whole table, so
4085    // the configured ceiling is applied as the limit in that case, and as an
4086    // upper bound when the query asks for more than it. A by-PK query resolves
4087    // to at most one row.
4088    let limit: Option<i64> = if is_by_pk {
4089        Some(1)
4090    } else {
4091        // The permission's ceiling is one more of the same kind, so it folds
4092        // in the same way: whichever of the three is smallest wins, and a
4093        // request naming none takes the lowest that was named.
4094        let ceiling = match (
4095            max_rows,
4096            permission_limit(
4097                &gql_ctx.caller(),
4098                spec.names.as_ref(),
4099                schema_name,
4100                table_name,
4101            ),
4102        ) {
4103            (Some(configured), Some(granted)) => Some(configured.min(granted)),
4104            (only, None) | (None, only) => only,
4105        };
4106        match (requested_limit, ceiling) {
4107            (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
4108            (Some(requested), None) => Some(requested),
4109            (None, ceiling) => ceiling,
4110        }
4111    };
4112
4113    // Build the WHERE clause.
4114    //
4115    // A by-PK query filters on the table's key columns; each value is bound as
4116    // a parameter and cast to the column's type, since GraphQL scalars and
4117    // PostgreSQL types do not line up (a `uuid` key arrives as a String). A
4118    // list query filters on the `filter` argument, which takes the same shape
4119    // as a mutation's `where`.
4120    let mut where_sql = String::new();
4121    let mut bound_values: Vec<serde_json::Value> = Vec::new();
4122
4123    // Where the rows come from. A function's own arguments are bound before
4124    // anything else, because they sit in the FROM clause and every other
4125    // parameter is numbered after them.
4126    let source = match &spec.call {
4127        // Aliased rather than named. A whole-row reference -- what a computed
4128        // field is passed -- can only be written as a bare name, and
4129        // `"public"."author"` is not one: PostgreSQL reads it as a column of a
4130        // table called `public`. An alias no column can share is a name that
4131        // works in both positions.
4132        None => format!(
4133            "{}.{} AS {}",
4134            postrust_sql::escape_ident(schema_name),
4135            postrust_sql::escape_ident(table_name),
4136            postrust_sql::escape_ident(READ_ROW)
4137        ),
4138        Some(call) => function_source(ctx, call, table_name, &mut bound_values)?,
4139    };
4140
4141    // How a column of the source is referred to: a table by its qualified
4142    // name, a function's rows by the alias above.
4143    let source_ref = match &spec.call {
4144        None => postrust_sql::escape_ident(READ_ROW),
4145        Some(_) => postrust_sql::escape_ident(table_name),
4146    };
4147
4148    if is_by_pk {
4149        if pk_columns.is_empty() {
4150            return Err(async_graphql::Error::new(format!(
4151                "\"{}\" has no primary key, so it cannot be queried by key",
4152                table_name
4153            )));
4154        }
4155
4156        let mut conditions = Vec::with_capacity(pk_columns.len());
4157        for (idx, (col_name, pg_type)) in pk_columns.iter().enumerate() {
4158            let field = spec
4159                .names
4160                .column(schema_name, table_name, col_name)
4161                .unwrap_or(col_name);
4162            let value = ctx.args.try_get(field).map_err(|_| {
4163                async_graphql::Error::new(format!(
4164                    "missing required primary key argument \"{}\"",
4165                    field
4166                ))
4167            })?;
4168
4169            conditions.push(format!(
4170                "{} = ${}::{}",
4171                postrust_sql::escape_ident(col_name),
4172                idx + 1,
4173                pg_type
4174            ));
4175            bound_values.push(accessor_to_json(&value));
4176        }
4177        where_sql = format!(" WHERE {}", conditions.join(" AND "));
4178    }
4179
4180    // What this role may read, beside what the request asked for. Applied to
4181    // both shapes and to neither: a query naming no `where` at all is still a
4182    // read, and a by-key query that skipped the permission would be the one
4183    // way to fetch a row the filter withholds.
4184    {
4185        let requested = match is_by_pk {
4186            true => None,
4187            false => ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v)),
4188        };
4189        let permission = permission_predicate(
4190            &gql_ctx.caller(),
4191            spec.names.as_ref(),
4192            schema_name,
4193            table_name,
4194        )?;
4195
4196        if let Some(predicate) = and_predicates(requested, permission) {
4197            let guard = gql_ctx
4198                .schema_cache
4199                .get()
4200                .await
4201                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4202            let cache = guard
4203                .as_ref()
4204                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4205            let scope = match &spec.call {
4206                None => WhereScope::table(schema_name, table_name, type_name, spec.names.as_ref())
4207                    .under_alias(READ_ROW)
4208                    .with_resolution(cache, relationships)
4209                    .for_caller(gql_ctx.caller()),
4210                // A function's rows are the table's rows, so they are renamed
4211                // the same way; only how they are reached differs.
4212                Some(_) => WhereScope::for_alias(
4213                    schema_name,
4214                    table_name,
4215                    table_name,
4216                    type_name,
4217                    cache,
4218                    relationships,
4219                    spec.names.as_ref(),
4220                )
4221                .for_caller(gql_ctx.caller()),
4222            };
4223            let (filter_sql, filter_values) =
4224                build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
4225            if !filter_sql.is_empty() {
4226                // A by-key query already has a `WHERE`, so this joins it
4227                // rather than replacing it.
4228                where_sql = match where_sql.is_empty() {
4229                    true => format!(" {}", filter_sql),
4230                    false => format!(
4231                        "{} AND ({})",
4232                        where_sql,
4233                        filter_sql.trim_start_matches("WHERE ")
4234                    ),
4235                };
4236                bound_values.extend(filter_values);
4237            }
4238        }
4239    }
4240
4241    // A by-key query resolves to one row, so neither ordering nor distinct
4242    // has anything to do.
4243    let (order_sql, distinct_on) = if is_by_pk {
4244        (String::new(), Vec::new())
4245    } else {
4246        (
4247            build_order_by_clause(
4248                ctx,
4249                &gql_ctx.schema_cache,
4250                schema_name,
4251                table_name,
4252                type_name,
4253                relationships,
4254                &source_ref,
4255                spec.names.as_ref(),
4256            )
4257            .await?,
4258            build_distinct_on(
4259                ctx,
4260                &gql_ctx.schema_cache,
4261                schema_name,
4262                table_name,
4263                spec.names.as_ref(),
4264            )
4265            .await?,
4266        )
4267    };
4268
4269    // PostgreSQL keeps the first row of each DISTINCT ON group in the query's
4270    // own order, and picks arbitrarily where the ordering does not begin with
4271    // the distinct columns -- so which row survives would depend on the plan.
4272    // Hasura refuses that query rather than answering it, and so does this:
4273    // prepending the distinct columns instead produced an answer, and a wrong
4274    // one, since `ORDER BY "department", "department" DESC` is decided by its
4275    // first term and sorts ascending.
4276    let (distinct_sql, order_sql) = {
4277        let written = order_sql.strip_prefix(" ORDER BY ");
4278        // Reported against the field's arguments as a whole: the rule is about
4279        // two of them disagreeing, so neither one is where it went wrong.
4280        let (prefix, order) = distinct_on_clause(&distinct_on, written).map_err(|error| {
4281            at_path(
4282                error,
4283                &format!(
4284                    "$.selectionSet.{}.args",
4285                    ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4286                ),
4287            )
4288        })?;
4289        (
4290            prefix,
4291            match order {
4292                Some(order) => format!(" ORDER BY {}", order),
4293                None => String::new(),
4294            },
4295        )
4296    };
4297
4298    // A computed column is a function of the row rather than part of it, so
4299    // it is not in `*` and is named only when it was asked for.
4300    let (computed, row_column_types) = {
4301        let guard = gql_ctx
4302            .schema_cache
4303            .get()
4304            .await
4305            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4306        match guard.as_ref() {
4307            Some(cache) => {
4308                let qi =
4309                    postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
4310                match cache.get_table(&qi) {
4311                    Some(table) => {
4312                        let mut param_idx = bound_values.len() + 1;
4313                        (
4314                            computed_projections(
4315                                table,
4316                                ctx.field().selection_set(),
4317                                "src",
4318                                spec.names.as_ref(),
4319                                cache,
4320                                &mut param_idx,
4321                                &mut bound_values,
4322                            )?,
4323                            exposed_column_types(table, spec.names.as_ref()),
4324                        )
4325                    }
4326                    None => (Vec::new(), HashMap::new()),
4327                }
4328            }
4329            None => (Vec::new(), HashMap::new()),
4330        }
4331    };
4332    // ORDER BY, LIMIT and OFFSET belong inside the subquery: applying them to
4333    // the outer `row_to_json` projection would leave the ordering of the rows
4334    // that survive the limit unspecified.
4335    let mut inner = format!(
4336        "SELECT {}* FROM {}{}{}",
4337        distinct_sql, source, where_sql, order_sql
4338    );
4339
4340    if let Some(limit) = limit {
4341        inner.push_str(&format!(" LIMIT {}", limit));
4342    }
4343
4344    if let Some(offset) = offset {
4345        inner.push_str(&format!(" OFFSET {}", offset));
4346    }
4347
4348    // Embed the requested relationships in this same query, as correlated
4349    // subselects in the SELECT list, so the whole selection is one round trip.
4350    let embed_expressions = {
4351        let guard = gql_ctx
4352            .schema_cache
4353            .get()
4354            .await
4355            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4356        match guard.as_ref() {
4357            Some(cache) => {
4358                // Parameters bound inside an embed continue the outer query's
4359                // numbering: sqlx binds by position, so the values only have
4360                // to be pushed in the order their placeholders were handed
4361                // out.
4362                let mut param_idx = bound_values.len() + 1;
4363                build_embed_expressions(
4364                    &gql_ctx.caller(),
4365                    cache,
4366                    relationships,
4367                    type_name,
4368                    "src",
4369                    ctx.field().selection_set(),
4370                    max_rows,
4371                    &mut 0,
4372                    &mut param_idx,
4373                    &mut bound_values,
4374                    spec.names.as_ref(),
4375                )?
4376            }
4377            None => Vec::new(),
4378        }
4379    };
4380
4381    // A computed column is a function of the row, and the row it is a function
4382    // of is the table's -- not the subquery's. Projecting it inside the
4383    // subquery gave `src` an extra column, which is what made a computed
4384    // relationship beside it fail with `cannot cast type record to author`: a
4385    // subquery alias is only passable as a composite value while its columns
4386    // are exactly the table's. So both live out here, where `src` still is
4387    // one.
4388    let renamed = {
4389        let guard = gql_ctx
4390            .schema_cache
4391            .get()
4392            .await
4393            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4394        guard
4395            .as_ref()
4396            .and_then(|cache| {
4397                cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
4398                    schema_name,
4399                    table_name,
4400                ))
4401            })
4402            .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4403    };
4404    let inner = if embed_expressions.is_empty() && computed.is_empty() && renamed.is_none() {
4405        inner
4406    } else {
4407        let mut projection = renamed.unwrap_or_else(|| "src.*".to_string());
4408        for expression in &computed {
4409            projection.push_str(", ");
4410            projection.push_str(expression);
4411        }
4412        for (field_name, expression) in &embed_expressions {
4413            projection.push_str(", ");
4414            projection.push_str(expression);
4415            projection.push_str(" AS ");
4416            projection.push_str(&postrust_sql::escape_ident(field_name));
4417        }
4418        format!("SELECT {} FROM ({}) AS src", projection, inner)
4419    };
4420
4421    let sql = format!(
4422        "SELECT {} FROM ({}) t",
4423        row_json("t", &row_column_types),
4424        inner
4425    );
4426
4427    // One transaction for the query and any embeds hanging off it, so the role
4428    // applies to all of them and the parent and child rows come from a single
4429    // snapshot.
4430    let mut tx = begin_with_session(pool, gql_ctx).await?;
4431
4432    // Execute query - returns Vec<serde_json::Value>
4433    let mut result = execute_query_on(&mut tx, &sql, &bound_values).await?;
4434
4435    // Anything the single-query form did not cover.
4436    if embed_expressions.is_empty() {
4437        let guard = gql_ctx
4438            .schema_cache
4439            .get()
4440            .await
4441            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4442        let cache = guard
4443            .as_ref()
4444            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4445
4446        let embed_ctx = EmbedContext {
4447            schema_cache: cache,
4448            relationships,
4449            max_rows,
4450        };
4451
4452        embed_relationships(&mut tx, &embed_ctx, type_name, ctx.field(), &mut result).await?;
4453    }
4454
4455    tx.commit().await?;
4456
4457    Ok(result)
4458}
4459
4460/// The rows a field answers with, in the shape its type says.
4461///
4462/// A by-key field is one row or none; every other is a list, empty where
4463/// nothing matched rather than null.
4464fn rows_as_field_value<'a>(rows: Vec<serde_json::Value>, is_by_pk: bool) -> Option<FieldValue<'a>> {
4465    match is_by_pk {
4466        true => rows
4467            .into_iter()
4468            .next()
4469            .map(|v| FieldValue::value(json_to_value(v))),
4470        false => Some(FieldValue::list(
4471            rows.into_iter()
4472                .map(|v| FieldValue::value(json_to_value(v))),
4473        )),
4474    }
4475}
4476
4477/// Resolve a mutation field.
4478#[allow(clippy::too_many_arguments)]
4479async fn resolve_mutation<'a>(
4480    ctx: &ResolverContext<'a>,
4481    schema_name: &str,
4482    table_name: &str,
4483    mutation_type: MutationType,
4484    pk_columns: &[(String, String)],
4485    relationships: &Arc<HashMap<String, Vec<RelationshipField>>>,
4486    type_names: &Arc<HashMap<(String, String), String>>,
4487    names: &Arc<crate::names::NameOverrides>,
4488    max_rows: Option<i64>,
4489) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4490    let pool = ctx.data::<PgPool>()?;
4491    let gql_ctx = ctx.data::<GraphQLContext>()?;
4492
4493    debug!(
4494        "Resolving mutation for table: {} type: {:?}",
4495        table_name, mutation_type
4496    );
4497
4498    // A relationship or a computed field asked for beside the written columns
4499    // is not in `RETURNING`, so the rows are read again through the projection
4500    // an ordinary query uses -- and only when the selection asks for something
4501    // that needs it. A delete cannot do that afterwards and builds it into the
4502    // statement instead, which is why this is settled before the statement is.
4503    let type_name = type_names
4504        .get(&(schema_name.to_string(), table_name.to_string()))
4505        .cloned()
4506        .unwrap_or_else(|| table_name.to_string());
4507
4508    let by_key = matches!(
4509        mutation_type,
4510        MutationType::InsertOne | MutationType::UpdateByPk | MutationType::DeleteByPk
4511    );
4512    let returning = if by_key {
4513        Some(ctx.field())
4514    } else {
4515        ctx.field()
4516            .selection_set()
4517            .find(|selection| selection.name() == "returning")
4518    };
4519
4520    // Several updates, each with its own filter, applied in the order given.
4521    // They share the operation's transaction like any other write, which is
4522    // the whole difference from sending them one at a time: either all of them
4523    // happened or none did.
4524    if mutation_type == MutationType::UpdateMany {
4525        let updates = ctx
4526            .args
4527            .try_get("updates")
4528            .ok()
4529            .map(|v| accessor_to_json(&v))
4530            .unwrap_or(serde_json::Value::Null);
4531        let serde_json::Value::Array(entries) = updates else {
4532            return Err(async_graphql::Error::new(
4533                "\"updates\" takes a list of {where, _set, …} objects",
4534            ));
4535        };
4536        let column_types = column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await;
4537        let mut answers: Vec<FieldValue<'_>> = Vec::with_capacity(entries.len());
4538        for entry in entries {
4539            let serde_json::Value::Object(entry) = entry else {
4540                return Err(async_graphql::Error::new(
4541                    "each update is an object of {where, _set, …}",
4542                ));
4543            };
4544            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4545                .iter()
4546                .filter_map(|name| entry.get(*name).map(|value| (*name, value.clone())))
4547                .filter(|(_, value)| !value.is_null())
4548                .collect();
4549            let rows = execute_update(
4550                pool,
4551                gql_ctx,
4552                schema_name,
4553                table_name,
4554                &type_name,
4555                operators,
4556                column_types.clone(),
4557                entry.get("where").cloned(),
4558                relationships.as_ref(),
4559                names.as_ref(),
4560            )
4561            .await?;
4562            let affected = rows.len();
4563            let rows = match returning {
4564                Some(returning) if !rows.is_empty() => {
4565                    reread_returning(
4566                        pool,
4567                        gql_ctx,
4568                        schema_name,
4569                        table_name,
4570                        &type_name,
4571                        rows,
4572                        returning,
4573                        relationships.as_ref(),
4574                        names.as_ref(),
4575                        max_rows,
4576                    )
4577                    .await?
4578                }
4579                _ => rows,
4580            };
4581            answers.extend(mutation_result(rows, affected, false));
4582        }
4583        return Ok(Some(FieldValue::list(answers)));
4584    }
4585
4586    let (result, affected) = match mutation_type {
4587        MutationType::Insert | MutationType::InsertOne => {
4588            let objects = ctx
4589                .args
4590                .try_get("objects")
4591                .ok()
4592                .map(|v| accessor_to_json(&v))
4593                .or_else(|| {
4594                    // `insert_x_one(object: {...})` is one row spelled without
4595                    // the list.
4596                    ctx.args
4597                        .try_get("object")
4598                        .ok()
4599                        .map(|v| serde_json::Value::Array(vec![accessor_to_json(&v)]))
4600                })
4601                .unwrap_or_else(|| serde_json::Value::Array(vec![]));
4602
4603            let guard = gql_ctx
4604                .schema_cache
4605                .get()
4606                .await
4607                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4608            let cache = guard
4609                .as_ref()
4610                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4611            // Where this write is, in the document the client sent. The
4612            // field as it was written -- an alias if there was one, since that
4613            // is what names this selection.
4614            let here = format!(
4615                "$.selectionSet.{}",
4616                ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4617            );
4618            // `insert_x(objects: [...])` against `insert_x_one(object: {...})`:
4619            // the same write under two spellings, and the path says which.
4620            let named = match mutation_type {
4621                MutationType::InsertOne => "object",
4622                _ => "objects",
4623            };
4624            let context = InsertContext {
4625                on_conflict: ctx
4626                    .args
4627                    .try_get("on_conflict")
4628                    .ok()
4629                    .map(|v| accessor_to_json(&v))
4630                    .filter(|v| !v.is_null()),
4631                cache,
4632                relationships: relationships.as_ref(),
4633                type_names: type_names.as_ref(),
4634                names: names.as_ref(),
4635                caller: gql_ctx.caller(),
4636                // Filled in per row by `execute_insert`, which is where the
4637                // index is known.
4638                row: String::new(),
4639                objects: format!("{}.args.{}", here, named),
4640                conflict: format!("{}.args.on_conflict", here),
4641            };
4642
4643            execute_insert(
4644                pool,
4645                gql_ctx,
4646                schema_name,
4647                table_name,
4648                objects,
4649                &context,
4650                mutation_type == MutationType::InsertOne,
4651            )
4652            .await?
4653        }
4654        // `UpdateMany` answered above: it is a list of responses rather than
4655        // one, so it does not share this shape.
4656        MutationType::UpdateMany | MutationType::Update | MutationType::UpdateByPk => {
4657            // `_set` replaces, the others read the column they write. A client
4658            // may send more than one, so all of them are collected rather than
4659            // the first that happens to be present.
4660            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4661                .iter()
4662                .filter_map(|name| {
4663                    ctx.args
4664                        .try_get(name)
4665                        .ok()
4666                        .map(|v| (*name, accessor_to_json(&v)))
4667                })
4668                .filter(|(_, value)| !value.is_null())
4669                .collect();
4670
4671            let where_clause = if mutation_type == MutationType::UpdateByPk {
4672                Some(pk_where_from_args(
4673                    ctx,
4674                    schema_name,
4675                    table_name,
4676                    pk_columns,
4677                    names.as_ref(),
4678                )?)
4679            } else {
4680                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4681            };
4682            // Which rows this role may change, beside which rows the request
4683            // asked to change. A by-key update is narrowed too: naming the key
4684            // of a row the permission withholds must not be a way to reach it.
4685            let where_clause = and_predicates(
4686                where_clause,
4687                permission_filter(
4688                    &gql_ctx.caller(),
4689                    names.as_ref(),
4690                    schema_name,
4691                    table_name,
4692                    crate::role::Verb::Update,
4693                )?,
4694            );
4695
4696            execute_update(
4697                pool,
4698                gql_ctx,
4699                schema_name,
4700                table_name,
4701                &type_name,
4702                operators,
4703                column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await,
4704                where_clause,
4705                relationships.as_ref(),
4706                names.as_ref(),
4707            )
4708            .await
4709            .map(|rows| {
4710                let count = rows.len();
4711                (rows, count)
4712            })?
4713        }
4714        MutationType::Delete | MutationType::DeleteByPk => {
4715            let where_clause = if mutation_type == MutationType::DeleteByPk {
4716                Some(pk_where_from_args(
4717                    ctx,
4718                    schema_name,
4719                    table_name,
4720                    pk_columns,
4721                    names.as_ref(),
4722                )?)
4723            } else {
4724                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4725            };
4726            let where_clause = and_predicates(
4727                where_clause,
4728                permission_filter(
4729                    &gql_ctx.caller(),
4730                    names.as_ref(),
4731                    schema_name,
4732                    table_name,
4733                    crate::role::Verb::Delete,
4734                )?,
4735            );
4736
4737            execute_delete(
4738                pool,
4739                gql_ctx,
4740                schema_name,
4741                table_name,
4742                &type_name,
4743                where_clause,
4744                returning,
4745                relationships.as_ref(),
4746                names.as_ref(),
4747                max_rows,
4748            )
4749            .await
4750            .map(|rows| {
4751                let count = rows.len();
4752                (rows, count)
4753            })?
4754        }
4755    };
4756
4757    let is_delete = matches!(
4758        mutation_type,
4759        MutationType::Delete | MutationType::DeleteByPk
4760    );
4761    let result = match returning {
4762        Some(returning) if !result.is_empty() && !is_delete => {
4763            reread_returning(
4764                pool,
4765                gql_ctx,
4766                schema_name,
4767                table_name,
4768                &type_name,
4769                result,
4770                returning,
4771                relationships.as_ref(),
4772                names.as_ref(),
4773                max_rows,
4774            )
4775            .await?
4776        }
4777        _ => result,
4778    };
4779
4780    Ok(mutation_result(result, affected, by_key))
4781}
4782
4783/// The transaction this operation's writes share, opening it if this is the
4784/// first one.
4785///
4786/// Held for the length of the write, and settled by whoever answers the
4787/// request -- see [`crate::context::SharedWrite`]. Mutation root fields are
4788/// resolved one after another, so the lock is never contended; it is what
4789/// makes the transaction reachable from a resolver that owns nothing.
4790async fn write_tx<'a>(
4791    gql_ctx: &'a GraphQLContext,
4792    pool: &PgPool,
4793) -> Result<
4794    tokio::sync::MutexGuard<'a, Option<sqlx::Transaction<'static, sqlx::Postgres>>>,
4795    async_graphql::Error,
4796> {
4797    let mut guard = gql_ctx.write.lock().await;
4798    if guard.is_none() {
4799        *guard = Some(begin_with_session(pool, gql_ctx).await?);
4800    }
4801    Ok(guard)
4802}
4803
4804/// Begin a transaction that runs as this request's caller.
4805///
4806/// Every transaction GraphQL opens starts here -- a query's, an aggregate's,
4807/// a federated entity lookup's, a subscription's re-read, and the one a
4808/// mutation's writes share -- and each is set up by the request's
4809/// [`postrust_core::Session`], the same statements REST's transactions start
4810/// with. See [`GraphQLContext::session`] for what goes in and why.
4811///
4812/// This used to be written out here: the settings in one `set_config`, then
4813/// `SET LOCAL ROLE`. It differed from REST in what it left out -- a role's own
4814/// `statement_timeout` and isolation level, `PGRST_DB_TX_ISOLATION`, the
4815/// `search_path`, `app.settings.*` -- and in the order, which put the role
4816/// last. The session sets the role inside the one statement, after the
4817/// settings PostgREST puts before it and before the claims and the `hasura.*`
4818/// variables; every one of those is a setting any role may set on itself, so
4819/// they are set as the caller as readily as they were as the authenticator.
4820async fn begin_with_session(
4821    pool: &PgPool,
4822    gql_ctx: &GraphQLContext,
4823) -> Result<sqlx::Transaction<'static, sqlx::Postgres>, async_graphql::Error> {
4824    let mut tx = pool.begin().await?;
4825    gql_ctx.session().apply_sqlx(&mut tx).await?;
4826    Ok(tx)
4827}
4828
4829/// Render a row as JSON, with any shape column rendered as GeoJSON.
4830///
4831/// PostgreSQL's text form for a geometry is WKB hex --
4832/// `0103000020E6100000…` -- which is what `row_to_json` puts in the response
4833/// and is not what any client can read. Hasura answers GeoJSON, and a client
4834/// migrating from it parses GeoJSON.
4835///
4836/// The shape columns are merged over the row rather than replacing the
4837/// projection, so a table with no shape column produces exactly the SQL it did
4838/// before and one with three needs no list of the others.
4839fn row_json(expr: &str, column_types: &HashMap<String, String>) -> String {
4840    let mut shapes: Vec<(&String, &String)> = column_types
4841        .iter()
4842        .filter(|(_, pg_type)| matches!(pg_type.as_str(), "geometry" | "geography"))
4843        .collect();
4844    if shapes.is_empty() {
4845        return format!("row_to_json({})", expr);
4846    }
4847    shapes.sort();
4848
4849    let overrides: Vec<String> = shapes
4850        .iter()
4851        .map(|(name, _)| {
4852            format!(
4853                // Option 4 asks PostGIS for the long-form CRS member --
4854                // `urn:ogc:def:crs:EPSG::4326` -- which Hasura includes and
4855                // which a client round-tripping a shape needs in order to
4856                // write it back where it came from. PostGIS emits it only
4857                // where the geometry has an SRID to report.
4858                "{}, ST_AsGeoJSON({}.{}, 9, 4)::jsonb",
4859                postrust_sql::quote_literal(name),
4860                expr,
4861                postrust_sql::escape_ident(name)
4862            )
4863        })
4864        .collect();
4865    format!(
4866        "(to_jsonb({}) || jsonb_build_object({}))",
4867        expr,
4868        overrides.join(", ")
4869    )
4870}
4871
4872/// Execute a SQL query and return results as serde_json::Value.
4873/// We keep data as serde_json::Value so field resolvers can use try_downcast_ref.
4874async fn execute_query_on(
4875    conn: &mut sqlx::PgConnection,
4876    sql: &str,
4877    params: &[serde_json::Value],
4878) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4879    use sqlx::Row;
4880
4881    trace!("Executing SQL: {}", sql);
4882
4883    // Execute query
4884    let mut query = sqlx::query(AssertSqlSafe(sql));
4885    for param in params {
4886        query = bind_json_value(query, param);
4887    }
4888    let rows = query.fetch_all(&mut *conn).await.map_err(database_error)?;
4889
4890    // Return raw JSON values - don't convert to async_graphql::Value
4891    // This allows field resolvers to use try_downcast_ref::<serde_json::Value>()
4892    let results: Vec<serde_json::Value> = rows
4893        .into_iter()
4894        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
4895        .collect();
4896
4897    Ok(results)
4898}
4899
4900/// Refuse a GeoJSON document PostGIS would accept and should not.
4901///
4902/// `ST_GeomFromGeoJSON` will build a "Polygon" from three points that do not
4903/// meet, and a "LineString" from one point -- neither of which is a shape, and
4904/// both of which are then stored and read back as though they were. Hasura
4905/// checks the document before it reaches the database, refuses with
4906/// `parse-failed`, and says which rule was broken; the messages here are its
4907/// messages, because a client that reports them to a user is reporting text it
4908/// already ships.
4909///
4910/// A value that is not an object is left alone: a string in a geometry column
4911/// is WKT or WKB hex, which is a different thing entirely.
4912fn check_geojson(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4913    fn refuse<T>(message: &str) -> Result<T, async_graphql::Error> {
4914        Err(coded_error("parse-failed", message))
4915    }
4916
4917    fn position(
4918        value: &serde_json::Value,
4919    ) -> Result<&Vec<serde_json::Value>, async_graphql::Error> {
4920        match value.as_array() {
4921            Some(items) if items.len() >= 2 && items.iter().all(|i| i.is_number()) => Ok(items),
4922            _ => refuse("A Position needs at least 2 elements"),
4923        }
4924    }
4925
4926    fn positions(
4927        value: Option<&serde_json::Value>,
4928        least: usize,
4929        what: &str,
4930    ) -> Result<Vec<Vec<serde_json::Value>>, async_graphql::Error> {
4931        let items = match value.and_then(|v| v.as_array()) {
4932            Some(items) => items,
4933            None => return refuse(&format!("A {} needs at least {} Positions", what, least)),
4934        };
4935        if items.len() < least {
4936            return refuse(&format!("A {} needs at least {} Positions", what, least));
4937        }
4938        items.iter().map(|item| position(item).cloned()).collect()
4939    }
4940
4941    fn ring(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4942        let points = positions(Some(value), 4, "LinearRing")?;
4943        match (points.first(), points.last()) {
4944            (Some(first), Some(last)) if first == last => Ok(()),
4945            _ => refuse("the first and last locations have to be equal for a LinearRing"),
4946        }
4947    }
4948
4949    fn polygon(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4950        let Some(rings) = value.as_array() else {
4951            return refuse("A LinearRing needs at least 4 Positions");
4952        };
4953        rings.iter().try_for_each(ring)
4954    }
4955
4956    let serde_json::Value::Object(map) = value else {
4957        return Ok(());
4958    };
4959    let Some(kind) = map.get("type").and_then(|v| v.as_str()) else {
4960        return Ok(());
4961    };
4962    let coordinates = map.get("coordinates");
4963    match kind {
4964        "Point" => {
4965            position(coordinates.unwrap_or(&serde_json::Value::Null))?;
4966        }
4967        "MultiPoint" => {
4968            positions(coordinates, 0, "MultiPoint")?;
4969        }
4970        "LineString" => {
4971            positions(coordinates, 2, "LineString")?;
4972        }
4973        "MultiLineString" => {
4974            for line in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4975                positions(Some(line), 2, "LineString")?;
4976            }
4977        }
4978        "Polygon" => polygon(coordinates.unwrap_or(&serde_json::Value::Null))?,
4979        "MultiPolygon" => {
4980            for one in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4981                polygon(one)?;
4982            }
4983        }
4984        "GeometryCollection" => {
4985            for geometry in map
4986                .get("geometries")
4987                .and_then(|v| v.as_array())
4988                .into_iter()
4989                .flatten()
4990            {
4991                check_geojson(geometry)?;
4992            }
4993        }
4994        other => return refuse(&format!("unexpected geometry type: {}", other)),
4995    }
4996    Ok(())
4997}
4998
4999/// The call that produces a computed field's value.
5000///
5001/// Two spellings of the same row, because the two calls need different ones.
5002/// A positional call takes `alias.*`, which is what a qualified table can be
5003/// written as; a named call takes the row as one value, which only a bare
5004/// alias is. Named notation is needed exactly where a session argument is,
5005/// since the row is then not the only parameter and may not be the first.
5006fn computed_call(
5007    definition: &postrust_core::schema_cache::ComputedColumn,
5008    row: &str,
5009    row_by_name: &str,
5010) -> String {
5011    let function = format!(
5012        "{}.{}",
5013        postrust_sql::escape_ident(&definition.function.schema),
5014        postrust_sql::escape_ident(&definition.function.name)
5015    );
5016    match (&definition.session_argument, &definition.row_argument) {
5017        (Some(session), Some(row_argument)) => format!(
5018            "{}({} => {}, {} => {})",
5019            function,
5020            postrust_sql::escape_ident(row_argument),
5021            row_by_name,
5022            postrust_sql::escape_ident(session),
5023            SESSION_ARGUMENT
5024        ),
5025        _ => format!("{}({})", function, row),
5026    }
5027}
5028
5029/// What a `hasura_session` argument is given.
5030///
5031/// The session document, read from the setting [`GraphQLContext::session`]
5032/// writes.
5033/// `current_setting`'s second argument makes a missing setting null rather than
5034/// an error, which is the case of a request with no session at all.
5035const SESSION_ARGUMENT: &str = "coalesce(current_setting('hasura.session', true), '{}')::json";
5036
5037/// Everything an update may be told to do, in the order the schema declares
5038/// them. Read by both spellings: one update, and one of many.
5039const UPDATE_OPERATORS: [&str; 7] = [
5040    "_set",
5041    "_inc",
5042    "_append",
5043    "_prepend",
5044    "_delete_key",
5045    "_delete_elem",
5046    "_delete_at_path",
5047];
5048
5049/// What a read table is called inside its own query.
5050///
5051/// See [`WRITTEN_ROW`]: the row a query passes to a computed field has to be
5052/// named, and a table's qualified name is not a name a row can be written
5053/// under.
5054const READ_ROW: &str = "pgrst_src";
5055
5056/// What a written table is called inside its own statement.
5057///
5058/// See [`WhereScope::under_alias`]: the row a statement returns has to be
5059/// named, and a table's own name is read as a column first.
5060const WRITTEN_ROW: &str = "pgrst_row";
5061
5062/// The cast a written value needs to reach a column of this type.
5063///
5064/// A bound parameter arrives as text. PostgreSQL will coerce it to a numeric
5065/// or a date on assignment, but not to `jsonb`, an array, or a user-defined
5066/// type -- `column "details" is of type jsonb but expression is of type text`
5067/// is what an uncast insert answers. Naming the column's own type covers all
5068/// of them and changes nothing where the coercion would have worked anyway.
5069fn write_cast(column_types: &HashMap<String, String>, column: &str) -> String {
5070    match column_types.get(column) {
5071        Some(pg_type) => format!("::{}", pg_type),
5072        None => String::new(),
5073    }
5074}
5075
5076/// The value one column is written with, as it goes over the wire.
5077///
5078/// A `json` column holds a JSON value, and `"[]"` is one -- a string whose
5079/// characters happen to be brackets. Binding the bare text and casting stored
5080/// an empty array instead, so a client that wrote a string read back a list.
5081/// The document a column is given is the document it keeps.
5082fn write_operand(
5083    column_types: &HashMap<String, String>,
5084    column: &str,
5085    value: &serde_json::Value,
5086) -> serde_json::Value {
5087    match column_types.get(column).map(String::as_str) {
5088        Some("json") | Some("jsonb") => match value {
5089            serde_json::Value::String(_)
5090            | serde_json::Value::Number(_)
5091            | serde_json::Value::Bool(_) => serde_json::Value::String(value.to_string()),
5092            other => other.clone(),
5093        },
5094        // An array column takes an array literal. `["a","b"]` is JSON, and
5095        // PostgreSQL reads a text parameter destined for a `text[]` as
5096        // `{a,b}` -- `malformed array literal` is what it says about the
5097        // other spelling.
5098        Some(pg_type) if is_array_type(pg_type) => match value {
5099            serde_json::Value::Array(_) => serde_json::Value::String(array_literal(value)),
5100            other => other.clone(),
5101        },
5102        _ => value.clone(),
5103    }
5104}
5105
5106/// Whether a PostgreSQL type name is an array.
5107///
5108/// Both spellings appear in the catalogue depending on how it was asked:
5109/// `text[]` from `format_type`, `_text` from `typname`.
5110fn is_array_type(pg_type: &str) -> bool {
5111    pg_type.ends_with("[]") || pg_type.starts_with('_')
5112}
5113
5114/// A JSON array as PostgreSQL writes one.
5115///
5116/// `["a","b"]` becomes `{"a","b"}`. Every element is quoted, so a value
5117/// containing a comma, a brace or a backslash survives; a null is the unquoted
5118/// `NULL`, which is the only way to write one.
5119fn array_literal(value: &serde_json::Value) -> String {
5120    fn element(value: &serde_json::Value) -> String {
5121        match value {
5122            serde_json::Value::Null => "NULL".to_string(),
5123            serde_json::Value::Array(_) => array_literal(value),
5124            serde_json::Value::String(text) => {
5125                format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
5126            }
5127            other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
5128        }
5129    }
5130    let items = match value {
5131        serde_json::Value::Array(items) => items,
5132        other => return element(other),
5133    };
5134    format!(
5135        "{{{}}}",
5136        items.iter().map(element).collect::<Vec<_>>().join(",")
5137    )
5138}
5139
5140/// The expression that writes one bound value into one column.
5141///
5142/// A cast is enough for almost everything. A shape is the exception: a client
5143/// sends GeoJSON, which is what Hasura accepts, and `'{"type":"Point",…}'` is
5144/// not something PostgreSQL will cast to a geometry -- it has a function for
5145/// reading that, and the function is the only way in.
5146///
5147/// Only when the value actually is an object. A string in a geometry column is
5148/// WKT or WKB hex, which the cast does read, and passing that to
5149/// `ST_GeomFromGeoJSON` would refuse a perfectly good value.
5150fn write_expression(
5151    column_types: &HashMap<String, String>,
5152    column: &str,
5153    value: &serde_json::Value,
5154    placeholder: &str,
5155) -> String {
5156    let pg_type = column_types.get(column).map(String::as_str);
5157    if value.is_object() && matches!(pg_type, Some("geometry") | Some("geography")) {
5158        return match pg_type {
5159            Some("geography") => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
5160            _ => format!("ST_GeomFromGeoJSON({})", placeholder),
5161        };
5162    }
5163    // An amount written as a number is reached through one: PostgreSQL has no
5164    // cast from `double precision` to `money`, which is what a bound JSON
5165    // number arrives as, and `numeric` is the type it does take one from. One
5166    // written as text -- `"$12,344.57"` -- is read by `money` directly, and
5167    // going through `numeric` would refuse the currency symbol.
5168    if pg_type == Some("money") && value.is_number() {
5169        return format!("{}::numeric::money", placeholder);
5170    }
5171    format!("{}{}", placeholder, write_cast(column_types, column))
5172}
5173
5174/// Every column of a table with the type it is declared as.
5175async fn column_types_of(
5176    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
5177    schema_name: &str,
5178    table_name: &str,
5179) -> HashMap<String, String> {
5180    let Ok(guard) = schema_cache.get().await else {
5181        return HashMap::new();
5182    };
5183    let Some(cache) = guard.as_ref() else {
5184        return HashMap::new();
5185    };
5186    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5187    match cache.get_table(&qi) {
5188        Some(table) => table
5189            .columns
5190            .values()
5191            .map(|c| (c.name.clone(), c.nominal_type.clone()))
5192            .collect(),
5193        None => HashMap::new(),
5194    }
5195}
5196
5197/// Insert one row, and any rows nested inside it.
5198///
5199/// Hasura writes a parent and its children in one mutation:
5200///
5201/// ```graphql
5202/// insert_article(objects: [{title: "x", author: {data: {name: "y"}}}])
5203/// ```
5204///
5205/// Which row goes first follows from which side holds the key. A relationship
5206/// to one row is reached through a column of *this* table, so the related row
5207/// is inserted first and its key is what this row's column is set to. A
5208/// relationship to many rows is reached through a column of *theirs*, so this
5209/// row goes first and each child's column is set from it.
5210///
5211/// Everything runs in the transaction the caller opened, so a child that
5212/// cannot be written takes the parent with it -- which is what writing them in
5213/// one mutation means.
5214fn insert_row<'life>(
5215    conn: &'life mut sqlx::PgConnection,
5216    schema_name: &'life str,
5217    table_name: &'life str,
5218    object: serde_json::Map<String, serde_json::Value>,
5219    context: &'life InsertContext<'life>,
5220    written_count: &'life mut usize,
5221) -> std::pin::Pin<
5222    Box<
5223        dyn std::future::Future<Output = Result<serde_json::Value, async_graphql::Error>>
5224            + Send
5225            + 'life,
5226    >,
5227> {
5228    Box::pin(async move {
5229        use sqlx::Row;
5230
5231        let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5232        let table = context.cache.get_table(&qi).ok_or_else(|| {
5233            async_graphql::Error::new(format!("unknown table \"{}\"", table_name))
5234        })?;
5235        let column_types: HashMap<String, String> = table
5236            .columns
5237            .values()
5238            .map(|c| (c.name.clone(), c.nominal_type.clone()))
5239            .collect();
5240
5241        // A key that is a relationship rather than a column is a nested write.
5242        let type_name = context
5243            .type_names
5244            .get(&(schema_name.to_string(), table_name.to_string()));
5245        let relationships: &[RelationshipField] = type_name
5246            .and_then(|name| context.relationships.get(name))
5247            .map(|r| r.as_slice())
5248            .unwrap_or(&[]);
5249
5250        let mut columns = serde_json::Map::new();
5251        type Nested<'r> = (
5252            &'r RelationshipField,
5253            serde_json::Value,
5254            Option<serde_json::Value>,
5255        );
5256        let mut to_one: Vec<Nested> = Vec::new();
5257        let mut to_many: Vec<Nested> = Vec::new();
5258
5259        for (key, value) in object {
5260            match relationships.iter().find(|r| r.name == key) {
5261                None => {
5262                    // A written value is keyed by the field it was sent under;
5263                    // the statement is written in the table's own columns.
5264                    columns.insert(
5265                        table_column_for(context.names, table, &key).to_string(),
5266                        value,
5267                    );
5268                }
5269                Some(_) if value.is_null() => {
5270                    // `author: null` is a row with no author, not a row whose
5271                    // author is a null object. A client writing one insert for
5272                    // both cases sends the relationship either way.
5273                    continue;
5274                }
5275                Some(relationship) => {
5276                    // `{data: {...}}` for one row, `{data: [{...}]}` for many,
5277                    // and an `on_conflict` beside either -- a nested row is
5278                    // upserted the same way a top-level one is.
5279                    let (data, conflict) = match &value {
5280                        serde_json::Value::Object(map) => (
5281                            map.get("data").cloned().unwrap_or(value.clone()),
5282                            map.get("on_conflict").cloned().filter(|v| !v.is_null()),
5283                        ),
5284                        other => (other.clone(), None),
5285                    };
5286                    // Which row goes first follows from which side holds the
5287                    // key, not from how many rows there are. A one-to-one
5288                    // whose child key *is* the parent's -- `author_detail.id`
5289                    // referencing `author.id` -- is one row either way, and
5290                    // the parent still has to be written before there is a key
5291                    // to give it.
5292                    if child_holds_the_key(&relationship.relationship) {
5293                        to_many.push((relationship, data, conflict));
5294                    } else {
5295                        to_one.push((relationship, data, conflict));
5296                    }
5297                }
5298            }
5299        }
5300
5301        // A shape is checked before it is written, since PostGIS would build
5302        // one from a document that is not a shape.
5303        for (column, value) in &columns {
5304            if matches!(
5305                column_types.get(column).map(String::as_str),
5306                Some("geometry") | Some("geography")
5307            ) {
5308                // Under the name the client wrote it under, which is the
5309                // exposed one rather than the column's where they differ.
5310                check_geojson(value).map_err(|error| {
5311                    let field = context
5312                        .names
5313                        .column(schema_name, table_name, column)
5314                        .unwrap_or(column);
5315                    at_path(error, &format!("{}.{}", context.row, field))
5316                })?;
5317            }
5318        }
5319
5320        // The rows this one points at, first: this row's own column carries
5321        // their key.
5322        for (relationship, data, conflict) in to_one {
5323            let plan =
5324                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5325                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5326            let serde_json::Value::Object(child) = data else {
5327                return Err(async_graphql::Error::new(format!(
5328                    "\"{}\" takes an object to insert",
5329                    relationship.name
5330                )));
5331            };
5332            // Writing the related row is what fills this row's key column in,
5333            // so a value already written there is a second answer to the same
5334            // question and one of them would be silently discarded.
5335            for (local, _) in &plan.columns {
5336                if columns.get(local).is_some_and(|v| !v.is_null()) {
5337                    return Err(at_path(
5338                        validation_error(format!(
5339                            "cannot insert object relationship \"{}\" as \"{}\" column \
5340                             values are already determined",
5341                            relationship.name, local
5342                        )),
5343                        &format!("{}.{}", context.row, relationship.name),
5344                    ));
5345                }
5346            }
5347            // A to-one row is written under the relationship's own name:
5348            // `...objects[0].author.data`, and its `on_conflict` beside it.
5349            let under = format!("{}.{}", context.row, relationship.name);
5350            let nested = InsertContext {
5351                on_conflict: conflict,
5352                cache: context.cache,
5353                relationships: context.relationships,
5354                type_names: context.type_names,
5355                names: context.names,
5356                caller: context.caller,
5357                row: format!("{}.data", under),
5358                objects: format!("{}.data", under),
5359                conflict: format!("{}.on_conflict", under),
5360            };
5361            let written = insert_row(
5362                conn,
5363                &plan.foreign_schema,
5364                &plan.foreign_table,
5365                child,
5366                &nested,
5367                written_count,
5368            )
5369            .await?;
5370            for (local, foreign) in &plan.columns {
5371                if let Some(value) = written.get(foreign) {
5372                    columns.insert(local.clone(), value.clone());
5373                }
5374            }
5375        }
5376
5377        // What the server fills in regardless of what was sent. Applied after
5378        // the nested rows have contributed their keys and before the columns
5379        // are counted, because a preset is a column this statement writes --
5380        // and applied *over* the request, since a preset the client could
5381        // override is not a preset.
5382        for (column, value) in crate::role::presets(
5383            &context.caller,
5384            context.names,
5385            schema_name,
5386            table_name,
5387            crate::role::Verb::Insert,
5388        )
5389        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?
5390        {
5391            columns.insert(column, value);
5392        }
5393
5394        // `ON CONFLICT` says which uniqueness is being resolved against and
5395        // what to do about it. An empty `update_columns` is `DO NOTHING`,
5396        // which is how a client says "leave the row that is already there".
5397        // The columns this statement writes, settled before the conflict
5398        // clause so a predicate on it can number its parameters after theirs.
5399        let column_names: Vec<&str> = columns.keys().map(|k| k.as_str()).collect();
5400        let mut conflict_values: Vec<serde_json::Value> = Vec::new();
5401        let conflict_sql = match &context.on_conflict {
5402            Some(serde_json::Value::Object(spec)) => {
5403                let constraint = spec.get("constraint").and_then(|v| v.as_str());
5404                let Some(constraint) = constraint else {
5405                    return Err(async_graphql::Error::new(
5406                        "on_conflict needs a constraint to resolve against",
5407                    ));
5408                };
5409                if !table
5410                    .unique_constraints
5411                    .iter()
5412                    .any(|(name, _)| name == constraint)
5413                {
5414                    return Err(async_graphql::Error::new(format!(
5415                        "\"{}\" is not a unique constraint of \"{}\"",
5416                        constraint, table_name
5417                    )));
5418                }
5419                let updates: Vec<&str> = spec
5420                    .get("update_columns")
5421                    .and_then(|v| v.as_array())
5422                    .map(|items| items.iter().filter_map(|i| i.as_str()).collect())
5423                    .unwrap_or_default();
5424                // The member that stands for the columns this role has none
5425                // of. It is in the enum so that `on_conflict` exists at all;
5426                // naming it is the one thing it cannot be used for.
5427                if updates.contains(&PLACEHOLDER_COLUMN) {
5428                    return Err(at_path(
5429                        coded_error("validation-failed", "erroneous column name"),
5430                        &context.conflict,
5431                    ));
5432                }
5433
5434                if updates.is_empty() {
5435                    format!(
5436                        " ON CONFLICT ON CONSTRAINT {} DO NOTHING",
5437                        postrust_sql::escape_ident(constraint)
5438                    )
5439                } else {
5440                    // An `ON CONFLICT DO UPDATE` is an update, so the update
5441                    // permission's presets are written here too -- and a
5442                    // column a preset writes does not also take its value from
5443                    // `EXCLUDED`, which is what "a preset overrides the
5444                    // request" means on this side.
5445                    let preset = crate::role::presets(
5446                        &context.caller,
5447                        context.names,
5448                        schema_name,
5449                        table_name,
5450                        crate::role::Verb::Update,
5451                    )
5452                    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5453
5454                    // Numbered after the row's own values and before the
5455                    // predicate below, because `SET` is written before `WHERE`
5456                    // and the parameters are bound in the order they appear.
5457                    let mut param_idx = column_names.len() + 1;
5458                    let mut assignments: Vec<String> =
5459                        Vec::with_capacity(preset.len() + updates.len());
5460                    for (column, value) in &preset {
5461                        assignments.push(format!(
5462                            "{} = {}",
5463                            postrust_sql::escape_ident(column),
5464                            write_expression(
5465                                &column_types,
5466                                column,
5467                                value,
5468                                &format!("${}", param_idx)
5469                            )
5470                        ));
5471                        conflict_values.push(write_operand(&column_types, column, value));
5472                        param_idx += 1;
5473                    }
5474                    for field in &updates {
5475                        let column = table_column_for(context.names, table, field);
5476                        if preset.iter().any(|(written, _)| written == column) {
5477                            continue;
5478                        }
5479                        assignments.push(format!(
5480                            "{} = EXCLUDED.{}",
5481                            postrust_sql::escape_ident(column),
5482                            postrust_sql::escape_ident(column)
5483                        ));
5484                    }
5485                    // `where` on an upsert decides whether the row that is
5486                    // already there is overwritten -- "only if what I am
5487                    // writing is newer". It reads the existing row, which is
5488                    // what the statement's alias names.
5489                    //
5490                    // The update permission's own filter is ANDed into it: an
5491                    // upsert that overwrites a row is an update, and a role
5492                    // that may not update a row may not reach it by inserting
5493                    // over it. Compiling that filter is also where a session
5494                    // variable it names and the caller does not carry is
5495                    // noticed -- which is the answer Hasura gives before it
5496                    // writes anything.
5497                    let granted = permission_filter(
5498                        &context.caller,
5499                        context.names,
5500                        schema_name,
5501                        table_name,
5502                        crate::role::Verb::Update,
5503                    )?;
5504                    let requested = spec.get("where").filter(|f| !f.is_null()).cloned();
5505                    let condition = match and_predicates(requested, granted) {
5506                        Some(filter) => {
5507                            let filter = &filter;
5508                            let type_name = context
5509                                .type_names
5510                                .get(&(schema_name.to_string(), table_name.to_string()))
5511                                .map(String::as_str)
5512                                .unwrap_or(table_name);
5513                            let scope = WhereScope::table(
5514                                schema_name,
5515                                table_name,
5516                                type_name,
5517                                context.names,
5518                            )
5519                            .under_alias(WRITTEN_ROW)
5520                            .with_resolution(context.cache, context.relationships);
5521                            let mut alias_counter = 0usize;
5522                            build_condition(
5523                                filter,
5524                                &scope,
5525                                &mut param_idx,
5526                                &mut conflict_values,
5527                                &mut alias_counter,
5528                            )?
5529                            .map(|sql| format!(" WHERE {}", sql))
5530                            .unwrap_or_default()
5531                        }
5532                        None => String::new(),
5533                    };
5534                    format!(
5535                        " ON CONFLICT ON CONSTRAINT {} DO UPDATE SET {}{}",
5536                        postrust_sql::escape_ident(constraint),
5537                        assignments.join(", "),
5538                        condition
5539                    )
5540                }
5541            }
5542            _ => String::new(),
5543        };
5544
5545        // The row this statement writes, as `RETURNING` names it. Not the
5546        // table's own name: qualifying it reads as a column of a table called
5547        // `public`, which is what `missing FROM-clause entry for table
5548        // "public"` means, and leaving it bare reads as a column of the table
5549        // where one shares the name.
5550        let qualified_table = postrust_sql::escape_ident(WRITTEN_ROW);
5551
5552        // What the written row has to satisfy for the write to stand,
5553        // evaluated in `RETURNING` against the row as written. Its parameters
5554        // are numbered after the values and the conflict clause, since
5555        // `RETURNING` is last.
5556        let check_scope = WhereScope::for_alias(
5557            schema_name,
5558            table_name,
5559            WRITTEN_ROW,
5560            context
5561                .type_names
5562                .get(&(schema_name.to_string(), table_name.to_string()))
5563                .map(String::as_str)
5564                .unwrap_or(table_name),
5565            context.cache,
5566            context.relationships,
5567            context.names,
5568        );
5569        let (check_sql, check_values) = {
5570            let check = crate::role::write_check(
5571                &context.caller,
5572                context.names,
5573                schema_name,
5574                table_name,
5575                crate::role::Verb::Insert,
5576            )
5577            .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5578            match check {
5579                None => (String::new(), Vec::new()),
5580                Some(check) => {
5581                    let mut values = Vec::new();
5582                    let mut param_idx = column_names.len() + conflict_values.len() + 1;
5583                    let mut alias_counter = 0usize;
5584                    match build_condition(
5585                        &check,
5586                        &check_scope,
5587                        &mut param_idx,
5588                        &mut values,
5589                        &mut alias_counter,
5590                    )? {
5591                        None => (String::new(), Vec::new()),
5592                        Some(sql) => (
5593                            format!(
5594                                ", ({}) AS {}",
5595                                sql,
5596                                postrust_sql::escape_ident(CHECK_COLUMN)
5597                            ),
5598                            values,
5599                        ),
5600                    }
5601                }
5602            }
5603        };
5604
5605        let names = column_names;
5606        let written = if names.is_empty() {
5607            // Every column defaulted. `DEFAULT VALUES` is how SQL says that;
5608            // an empty column list is a syntax error.
5609            let sql = format!(
5610                "INSERT INTO {}.{} AS {} DEFAULT VALUES{} RETURNING {}{}",
5611                postrust_sql::escape_ident(schema_name),
5612                postrust_sql::escape_ident(table_name),
5613                qualified_table,
5614                conflict_sql,
5615                row_json(&qualified_table, &column_types),
5616                check_sql
5617            );
5618            let mut query = sqlx::query(AssertSqlSafe(sql));
5619            for value in &conflict_values {
5620                query = bind_json_value(query, value);
5621            }
5622            for value in &check_values {
5623                query = bind_json_value(query, value);
5624            }
5625            query
5626                .fetch_optional(&mut *conn)
5627                .await
5628                // A constraint the write violated is reported against the rows
5629                // the statement was given, which is where Hasura reports it.
5630                .map_err(|error| at_path(database_error(error), &context.objects))?
5631        } else {
5632            let placeholders: Vec<String> = names
5633                .iter()
5634                .enumerate()
5635                .map(|(i, column)| {
5636                    let placeholder = format!("${}", i + 1);
5637                    match columns.get(*column) {
5638                        Some(value) => write_expression(&column_types, column, value, &placeholder),
5639                        None => placeholder,
5640                    }
5641                })
5642                .collect();
5643            let sql = format!(
5644                "INSERT INTO {}.{} AS {} ({}) VALUES ({}){} RETURNING {}{}",
5645                postrust_sql::escape_ident(schema_name),
5646                postrust_sql::escape_ident(table_name),
5647                qualified_table,
5648                names
5649                    .iter()
5650                    .map(|c| postrust_sql::escape_ident(c))
5651                    .collect::<Vec<_>>()
5652                    .join(", "),
5653                placeholders.join(", "),
5654                conflict_sql,
5655                row_json(&qualified_table, &column_types),
5656                check_sql
5657            );
5658            trace!("Executing INSERT SQL: {}", sql);
5659            let mut query = sqlx::query(AssertSqlSafe(sql));
5660            for column in &names {
5661                if let Some(value) = columns.get(*column) {
5662                    query = bind_json_value(query, &write_operand(&column_types, column, value));
5663                }
5664            }
5665            for value in &conflict_values {
5666                query = bind_json_value(query, value);
5667            }
5668            for value in &check_values {
5669                query = bind_json_value(query, value);
5670            }
5671            query
5672                .fetch_optional(&mut *conn)
5673                .await
5674                // A constraint the write violated is reported against the rows
5675                // the statement was given, which is where Hasura reports it.
5676                .map_err(|error| at_path(database_error(error), &context.objects))?
5677        };
5678
5679        // `DO NOTHING` writes nothing and returns nothing, which is the answer
5680        // rather than an error: the row that was already there stays.
5681        let Some(written) = written else {
5682            return Ok(serde_json::Value::Null);
5683        };
5684        refuse_unchecked_rows(std::slice::from_ref(&written), !check_sql.is_empty())
5685            .map_err(|error| at_path(error, &context.objects))?;
5686        let row: serde_json::Value = written.try_get(0).unwrap_or(serde_json::Value::Null);
5687        *written_count += 1;
5688
5689        // Then the rows that point at this one, which need its key.
5690        for (relationship, data, conflict) in to_many {
5691            let plan =
5692                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5693                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5694            let children = match data {
5695                serde_json::Value::Array(items) => items,
5696                serde_json::Value::Object(map) => vec![serde_json::Value::Object(map)],
5697                serde_json::Value::Null => Vec::new(),
5698                _ => {
5699                    return Err(async_graphql::Error::new(format!(
5700                        "\"{}\" takes objects to insert",
5701                        relationship.name
5702                    )))
5703                }
5704            };
5705            for (child_index, child) in children.into_iter().enumerate() {
5706                let serde_json::Value::Object(mut child) = child else {
5707                    continue;
5708                };
5709                for (local, foreign) in &plan.columns {
5710                    // The parent's key is what makes the child a child, so a
5711                    // child that writes it too is asking for a different
5712                    // parent than the one it is nested inside.
5713                    if child.get(foreign).is_some_and(|v| !v.is_null()) {
5714                        return Err(at_path(
5715                            validation_error(format!(
5716                                "cannot insert \"{}\" columns as their values are already \
5717                                 being determined by parent insert",
5718                                foreign
5719                            )),
5720                            &format!(
5721                                "{}.{}{}",
5722                                context.row,
5723                                relationship.name,
5724                                match relationship.is_list {
5725                                    true => format!(".data[{}]", child_index),
5726                                    false => ".data".to_string(),
5727                                }
5728                            ),
5729                        ));
5730                    }
5731                    if let Some(value) = row.get(local) {
5732                        child.insert(foreign.clone(), value.clone());
5733                    }
5734                }
5735                // `...objects[0].articles.data[0]`, which is the path
5736                // Hasura answers for a child that writes its parent's key.
5737                // One row written after its parent -- a mapped object
5738                // relationship with `after_parent` -- has no index: there is
5739                // no list for it to be an item of.
5740                let at = match relationship.is_list {
5741                    true => format!(".data[{}]", child_index),
5742                    false => ".data".to_string(),
5743                };
5744                let under = format!("{}.{}", context.row, relationship.name);
5745                let nested = InsertContext {
5746                    on_conflict: conflict.clone(),
5747                    cache: context.cache,
5748                    relationships: context.relationships,
5749                    type_names: context.type_names,
5750                    names: context.names,
5751                    caller: context.caller,
5752                    row: format!("{}{}", under, at),
5753                    objects: format!("{}.data", under),
5754                    conflict: format!("{}.on_conflict", under),
5755                };
5756                insert_row(
5757                    conn,
5758                    &plan.foreign_schema,
5759                    &plan.foreign_table,
5760                    child,
5761                    &nested,
5762                    written_count,
5763                )
5764                .await?;
5765            }
5766        }
5767
5768        Ok(row)
5769    })
5770}
5771
5772/// What a nested insert needs to follow a relationship.
5773///
5774/// Cloned per row and per nested row, which costs three strings: the paths are
5775/// the only part that differs between them, and the rest is references and a
5776/// `Caller`.
5777#[derive(Clone)]
5778struct InsertContext<'a> {
5779    /// What to do when this row conflicts. A nested row carries its own, from
5780    /// the `on_conflict` beside its `data`.
5781    on_conflict: Option<serde_json::Value>,
5782    cache: &'a SchemaCache,
5783    relationships: &'a HashMap<String, Vec<RelationshipField>>,
5784    /// (schema, table) -> the GraphQL type name, which keys the relationship
5785    /// map. A table's type is not always its name: a second schema prefixes
5786    /// it, and a name may have been given.
5787    type_names: &'a HashMap<(String, String), String>,
5788    /// The names columns are exposed under, so a written value keyed by a
5789    /// field reaches the column it names.
5790    names: &'a crate::names::NameOverrides,
5791    /// Who is writing, so that a permission's presets and its check reach
5792    /// every row this insert writes -- including the nested ones, which are
5793    /// rows of other tables under other rules.
5794    caller: crate::role::Caller<'a>,
5795    /// Where this row is in the request, as Hasura writes such a place:
5796    /// `$.selectionSet.insert_author.args.objects[0]`, and one level down
5797    /// `$.selectionSet.insert_author.args.objects[0].articles.data[0]`. A
5798    /// column of it is that with the field's name appended.
5799    row: String,
5800    /// Where the rows this statement writes were named. What a refused `check`
5801    /// is reported against -- the argument, not the row inside it, which is
5802    /// what Hasura answers.
5803    objects: String,
5804    /// Where this statement's `on_conflict` was named.
5805    conflict: String,
5806}
5807
5808/// Re-read written rows so that a mutation's `returning` can carry
5809/// relationships and computed fields.
5810///
5811/// `RETURNING row_to_json(t.*)` gives the table's own columns and nothing
5812/// else, so a relationship asked for beside them had no value to resolve --
5813/// and a non-null list field with no value is an error, which meant a mutation
5814/// that had written its rows correctly answered as though it had failed. The
5815/// write is not in doubt; only the shape of the answer is.
5816///
5817/// So the rows are read again by their primary key, through the same
5818/// projection an ordinary query uses. One extra round trip, and only when the
5819/// selection actually asks for something `RETURNING` cannot give.
5820#[allow(clippy::too_many_arguments)]
5821async fn reread_returning(
5822    pool: &PgPool,
5823    gql_ctx: &GraphQLContext,
5824    schema_name: &str,
5825    table_name: &str,
5826    type_name: &str,
5827    rows: Vec<Value>,
5828    returning: async_graphql::SelectionField<'_>,
5829    relationships: &HashMap<String, Vec<RelationshipField>>,
5830    names: &crate::names::NameOverrides,
5831    max_rows: Option<i64>,
5832) -> Result<Vec<Value>, async_graphql::Error> {
5833    let guard = gql_ctx
5834        .schema_cache
5835        .get()
5836        .await
5837        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5838    let Some(cache) = guard.as_ref() else {
5839        return Ok(rows);
5840    };
5841    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5842    let Some(table) = cache.get_table(&qi) else {
5843        return Ok(rows);
5844    };
5845    if table.pk_cols.is_empty() {
5846        // Nothing to identify the rows by. The columns are still right.
5847        return Ok(rows);
5848    }
5849
5850    let mut param_idx = 1usize;
5851    let mut values: Vec<serde_json::Value> = Vec::new();
5852    let mut alias_counter = 0usize;
5853    let embeds = build_embed_expressions(
5854        &gql_ctx.caller(),
5855        cache,
5856        relationships,
5857        type_name,
5858        "src",
5859        returning.selection_set(),
5860        max_rows,
5861        &mut alias_counter,
5862        &mut param_idx,
5863        &mut values,
5864        names,
5865    )?;
5866    let computed = computed_projections(
5867        table,
5868        returning.selection_set(),
5869        "src",
5870        names,
5871        cache,
5872        &mut param_idx,
5873        &mut values,
5874    )?;
5875    // A rename is reason enough to read the rows again: `RETURNING` answers in
5876    // the table's own column names, which are not the names the client asked
5877    // under.
5878    let renames = names.renames_columns(&table.schema, &table.name);
5879    if embeds.is_empty() && computed.is_empty() && !renames {
5880        return Ok(rows);
5881    }
5882
5883    // One row per key written, in the order they were written.
5884    let mut conditions = Vec::with_capacity(rows.len());
5885    for row in &rows {
5886        let mut parts = Vec::with_capacity(table.pk_cols.len());
5887        for column in &table.pk_cols {
5888            let Value::Object(map) = row else {
5889                return Ok(rows);
5890            };
5891            let Some(value) = map.get(&async_graphql::Name::new(column)) else {
5892                return Ok(rows);
5893            };
5894            parts.push(format!(
5895                "{} = ${}",
5896                postrust_sql::escape_ident(column),
5897                param_idx
5898            ));
5899            values.push(value_to_json(value));
5900            param_idx += 1;
5901        }
5902        conditions.push(format!("({})", parts.join(" AND ")));
5903    }
5904    if conditions.is_empty() {
5905        return Ok(rows);
5906    }
5907
5908    let mut projection =
5909        rename_projection(table, "src", names).unwrap_or_else(|| "src.*".to_string());
5910    for expression in &computed {
5911        projection.push_str(", ");
5912        projection.push_str(expression);
5913    }
5914    for (name, expression) in &embeds {
5915        projection.push_str(", ");
5916        projection.push_str(expression);
5917        projection.push_str(" AS ");
5918        projection.push_str(&postrust_sql::escape_ident(name));
5919    }
5920
5921    let sql = format!(
5922        "SELECT {} FROM (SELECT {} FROM (SELECT * FROM {}.{} WHERE {}) AS src) AS pgrst_r",
5923        row_json("pgrst_r", &exposed_column_types(table, names)),
5924        projection,
5925        postrust_sql::escape_ident(schema_name),
5926        postrust_sql::escape_ident(table_name),
5927        conditions.join(" OR ")
5928    );
5929
5930    // The same transaction the write went into: the rows it is reading are the
5931    // rows that write produced, and nothing has committed yet.
5932    let mut guard = write_tx(gql_ctx, pool).await?;
5933    let conn = guard.as_mut().expect("write_tx opens one");
5934    let reread = execute_query_on(conn, &sql, &values).await?;
5935    drop(guard);
5936
5937    if reread.is_empty() {
5938        return Ok(rows);
5939    }
5940    Ok(reread.into_iter().map(json_to_value).collect())
5941}
5942
5943/// Execute an insert mutation.
5944async fn execute_insert(
5945    pool: &PgPool,
5946    gql_ctx: &GraphQLContext,
5947    schema_name: &str,
5948    table_name: &str,
5949    objects: serde_json::Value,
5950    context: &InsertContext<'_>,
5951    // Whether the client wrote one row rather than a list of them. Only the
5952    // error paths care: `insert_x_one(object: {...})` has no row to index, so
5953    // its rows are named `args.object` and not `args.object[0]`.
5954    single: bool,
5955) -> Result<(Vec<Value>, usize), async_graphql::Error> {
5956    trace!("Insert mutation for {}: {:?}", table_name, objects);
5957
5958    // Handle both array and single object
5959    let objects_array = match objects {
5960        serde_json::Value::Array(arr) => arr,
5961        serde_json::Value::Object(obj) => vec![serde_json::Value::Object(obj)],
5962        _ => {
5963            return Err(async_graphql::Error::new(
5964                "objects must be an array or object",
5965            ))
5966        }
5967    };
5968
5969    if objects_array.is_empty() {
5970        return Err(async_graphql::Error::new("objects cannot be empty"));
5971    }
5972
5973    let mut guard = write_tx(gql_ctx, pool).await?;
5974    let conn = guard.as_mut().expect("write_tx opens one");
5975
5976    let mut inserted: Vec<Value> = Vec::new();
5977    let mut written = 0usize;
5978
5979    for (index, object) in objects_array.into_iter().enumerate() {
5980        let serde_json::Value::Object(map) = object else {
5981            return Err(async_graphql::Error::new(
5982                "each object to insert is an object",
5983            ));
5984        };
5985        // This row's own place in the request, which is what an error inside
5986        // it is reported against.
5987        let context = InsertContext {
5988            row: match single {
5989                true => context.objects.clone(),
5990                false => format!("{}[{}]", context.objects, index),
5991            },
5992            ..context.clone()
5993        };
5994        let row = insert_row(conn, schema_name, table_name, map, &context, &mut written).await?;
5995        // A row `DO NOTHING` left alone is not in `returning` and is not in
5996        // `affected_rows` either: nothing was written, and the row that was
5997        // already there is not this mutation's to report.
5998        if !row.is_null() {
5999            inserted.push(json_to_value(row));
6000        }
6001    }
6002
6003    Ok((inserted, written))
6004}
6005
6006/// Shape a mutation's result for the field that asked for it.
6007///
6008/// A by-key mutation answers with the row, or with null where the key matched
6009/// nothing. Every other mutation answers with the table's mutation response,
6010/// which carries `affected_rows` alongside the rows themselves -- a client may
6011/// ask for no rows back and still need the count, which is the usual case for
6012/// a delete.
6013///
6014/// The count is passed in rather than taken from the rows, because a nested
6015/// insert writes more rows than it returns: two articles each carrying a new
6016/// author is four rows written and two returned, and four is the answer.
6017fn mutation_result<'a>(rows: Vec<Value>, affected: usize, by_key: bool) -> Option<FieldValue<'a>> {
6018    if by_key {
6019        return rows.into_iter().next().map(FieldValue::value);
6020    }
6021    let mut response = async_graphql::indexmap::IndexMap::new();
6022    response.insert(
6023        async_graphql::Name::new("affected_rows"),
6024        Value::from(affected),
6025    );
6026    response.insert(async_graphql::Name::new("returning"), Value::List(rows));
6027    Some(FieldValue::value(Value::Object(response)))
6028}
6029
6030/// Bind a JSON value to a sqlx query.
6031///
6032/// Every value a GraphQL request carries reaches the database through here, as
6033/// a bound parameter, and that is what makes the `AssertSqlSafe` around each
6034/// statement in this module true rather than hopeful. sqlx 0.9 takes only a
6035/// `&'static str` as SQL unless told otherwise, and the statements here are
6036/// built at runtime because their shape follows the request -- which columns,
6037/// which tables, how many rows. What is spliced in comes from the schema cache
6038/// or from this module, never from the request: identifiers, each quoted with
6039/// `postrust_sql::escape_ident` first, the catalog's type names, and SQL
6040/// written here.
6041fn bind_json_value<'q>(
6042    query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
6043    value: &serde_json::Value,
6044) -> sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments> {
6045    match value {
6046        serde_json::Value::Null => query.bind(None::<String>),
6047        serde_json::Value::Bool(b) => query.bind(*b),
6048        serde_json::Value::Number(n) => {
6049            if let Some(i) = n.as_i64() {
6050                query.bind(i)
6051            } else if let Some(f) = n.as_f64() {
6052                query.bind(f)
6053            } else {
6054                query.bind(n.to_string())
6055            }
6056        }
6057        serde_json::Value::String(s) => query.bind(s.clone()),
6058        _ => query.bind(value.to_string()),
6059    }
6060}
6061
6062/// Execute an update mutation.
6063#[allow(clippy::too_many_arguments)] // the predicate needs the schema it reads
6064async fn execute_update(
6065    pool: &PgPool,
6066    gql_ctx: &GraphQLContext,
6067    schema_name: &str,
6068    table_name: &str,
6069    type_name: &str,
6070    operators: Vec<(&'static str, serde_json::Value)>,
6071    column_types: HashMap<String, String>,
6072    where_clause: Option<serde_json::Value>,
6073    relationships: &HashMap<String, Vec<RelationshipField>>,
6074    names: &crate::names::NameOverrides,
6075) -> Result<Vec<Value>, async_graphql::Error> {
6076    use sqlx::Row;
6077
6078    trace!("Update mutation for {}: {:?}", table_name, operators);
6079
6080    // Build the SET clause. Each operator writes a column in terms of itself
6081    // except `_set`, which replaces it -- `_inc` adds, the jsonb operators
6082    // concatenate or remove, and a client may send several in one mutation as
6083    // long as they name different columns.
6084    let mut set_parts: Vec<String> = Vec::new();
6085    let mut set_values: Vec<serde_json::Value> = Vec::new();
6086    let mut param_idx = 1;
6087    let mut written: HashSet<String> = HashSet::new();
6088
6089    // What the permission writes whatever the request said. A preset overrides
6090    // rather than collides -- one a client could override is not a preset --
6091    // so these go in first and the operators below step around the columns
6092    // they name.
6093    //
6094    // This is also what makes an update with no `_set` at all a real update:
6095    // `update_resident(where: {...})` under a permission whose `set` names
6096    // `city` changes `city`, and answers with the rows it changed rather than
6097    // with nothing.
6098    let preset = crate::role::presets(
6099        &gql_ctx.caller(),
6100        names,
6101        schema_name,
6102        table_name,
6103        crate::role::Verb::Update,
6104    )
6105    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6106    let preset_columns: HashSet<&str> = preset.iter().map(|(name, _)| name.as_str()).collect();
6107    for (column, value) in &preset {
6108        let placeholder =
6109            write_expression(&column_types, column, value, &format!("${}", param_idx));
6110        set_parts.push(format!(
6111            "{} = {}",
6112            postrust_sql::escape_ident(column),
6113            placeholder
6114        ));
6115        set_values.push(write_operand(&column_types, column, value));
6116        param_idx += 1;
6117    }
6118
6119    for (operator, payload) in &operators {
6120        let serde_json::Value::Object(map) = payload else {
6121            return Err(async_graphql::Error::new(format!(
6122                "\"{}\" takes an object mapping columns to values",
6123                operator
6124            )));
6125        };
6126        for (field, value) in map {
6127            let column = &names
6128                .column_source(schema_name, table_name, field)
6129                .unwrap_or(field)
6130                .to_string();
6131            // A preset has this column. Not the duplicate-write error below:
6132            // the request is allowed to name it, and the permission wins.
6133            if preset_columns.contains(column.as_str()) {
6134                continue;
6135            }
6136            if !written.insert(column.clone()) {
6137                return Err(async_graphql::Error::new(format!(
6138                    "\"{}\" is written twice in one update; a column may be \
6139                     changed by one operator at a time",
6140                    column
6141                )));
6142            }
6143            let quoted = postrust_sql::escape_ident(column);
6144            // `_set` writes the column and `_inc` adds to it, so both take a
6145            // value of the column's type and are cast to it -- `money + $1`
6146            // is not an operator PostgreSQL has, and neither is `money +
6147            // double precision`, which is what a bound number arrives as. The
6148            // others already say what they need -- `::jsonb` for a
6149            // concatenation, `::text[]` for a path -- and casting twice would
6150            // be wrong for `_delete_elem`, whose operand is an integer rather
6151            // than a value of the column.
6152            let placeholder = match *operator {
6153                "_set" | "_inc" => {
6154                    write_expression(&column_types, column, value, &format!("${}", param_idx))
6155                }
6156                _ => format!("${}", param_idx),
6157            };
6158            // The assignment PostgreSQL needs, which for everything but `_set`
6159            // reads the column's current value.
6160            let assignment = match *operator {
6161                "_set" => format!("{} = {}", quoted, placeholder),
6162                "_inc" => format!("{} = {} + {}", quoted, quoted, placeholder),
6163                "_append" => format!("{} = {} || {}::jsonb", quoted, quoted, placeholder),
6164                "_prepend" => format!("{} = {}::jsonb || {}", quoted, placeholder, quoted),
6165                "_delete_key" => format!("{} = {} - {}::text", quoted, quoted, placeholder),
6166                "_delete_elem" => format!("{} = {} - {}::int", quoted, quoted, placeholder),
6167                // A path is a list of keys, and one parameter carrying
6168                // `["name","last"]` is a JSON array -- which PostgreSQL reads
6169                // as an array literal and refuses, `malformed array literal`.
6170                // The elements are bound one at a time into an `ARRAY[...]`,
6171                // the same way the key-existence comparisons take theirs.
6172                "_delete_at_path" => {
6173                    let Some(steps) = value.as_array() else {
6174                        return Err(async_graphql::Error::new(format!(
6175                            "\"_delete_at_path\" on \"{}\" takes a list of keys",
6176                            column
6177                        )));
6178                    };
6179                    let path = sql_array(steps, "text[]", &mut param_idx, &mut set_values);
6180                    set_parts.push(format!("{} = {} #- {}", quoted, quoted, path));
6181                    continue;
6182                }
6183                other => {
6184                    return Err(async_graphql::Error::new(format!(
6185                        "unsupported update operator \"{}\"",
6186                        other
6187                    )))
6188                }
6189            };
6190            if *operator == "_set"
6191                && matches!(
6192                    column_types.get(column).map(String::as_str),
6193                    Some("geometry") | Some("geography")
6194                )
6195            {
6196                check_geojson(value)?;
6197            }
6198            set_parts.push(assignment);
6199            set_values.push(match *operator {
6200                "_set" => write_operand(&column_types, column, value),
6201                _ => value.clone(),
6202            });
6203            param_idx += 1;
6204        }
6205    }
6206
6207    // An update that changes nothing changes no rows. Refusing it looks like
6208    // the stricter answer and is the wrong one: a client building `_set` from
6209    // a form the user submitted unchanged sends an empty object, and the
6210    // honest report of what happened is that nothing did.
6211    if set_parts.is_empty() {
6212        return Ok(Vec::new());
6213    }
6214
6215    // Build WHERE clause. It can follow a relationship, the same way a query's
6216    // can: `update_article(where: {author: {name: {_eq: "x"}}})` names the
6217    // rows by something they point at rather than by a column of their own.
6218    let guard = gql_ctx
6219        .schema_cache
6220        .get()
6221        .await
6222        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6223    let cache = guard
6224        .as_ref()
6225        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6226    let scope = WhereScope::table(schema_name, table_name, type_name, names)
6227        .under_alias(WRITTEN_ROW)
6228        .with_resolution(cache, relationships);
6229    let (where_sql, where_values) = build_where_clause(where_clause.as_ref(), param_idx, &scope)?;
6230
6231    // An absent or unrecognised `where` argument yields an empty clause, which
6232    // would update every row in the table. Refuse instead.
6233    if where_sql.is_empty() {
6234        return Err(async_graphql::Error::new(format!(
6235            "update on \"{}\" requires a `where` argument with at least one \
6236             recognised condition; refusing to update every row",
6237            table_name
6238        )));
6239    }
6240
6241    // What the changed row has to satisfy for the change to stand. Evaluated
6242    // in the `RETURNING` clause, against the row as written -- which is the
6243    // only place it can honestly be evaluated, and costs neither a re-read nor
6244    // a way to identify the rows afterwards.
6245    let (check_sql, check_values) = permission_check_sql(
6246        gql_ctx,
6247        names,
6248        schema_name,
6249        table_name,
6250        crate::role::Verb::Update,
6251        &scope,
6252        param_idx + where_values.len(),
6253    )?;
6254
6255    let sql = format!(
6256        "UPDATE {}.{} AS {} SET {} {} RETURNING {}{}",
6257        postrust_sql::escape_ident(schema_name),
6258        postrust_sql::escape_ident(table_name),
6259        postrust_sql::escape_ident(WRITTEN_ROW),
6260        set_parts.join(", "),
6261        where_sql,
6262        row_json(&postrust_sql::escape_ident(WRITTEN_ROW), &column_types),
6263        check_sql
6264    );
6265
6266    trace!("Executing UPDATE SQL: {}", sql);
6267
6268    // Build query with parameters
6269    let mut query = sqlx::query(AssertSqlSafe(sql));
6270
6271    // Bind SET values
6272    for val in &set_values {
6273        query = bind_json_value(query, val);
6274    }
6275
6276    // Bind WHERE values
6277    for val in &where_values {
6278        query = bind_json_value(query, val);
6279    }
6280
6281    // And the check's, last, because `RETURNING` is.
6282    for val in &check_values {
6283        query = bind_json_value(query, val);
6284    }
6285
6286    let mut guard = write_tx(gql_ctx, pool).await?;
6287    let conn = guard.as_mut().expect("write_tx opens one");
6288    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6289
6290    refuse_unchecked_rows(&rows, !check_sql.is_empty())?;
6291
6292    let updated: Vec<Value> = rows
6293        .iter()
6294        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6295        .map(json_to_value)
6296        .collect();
6297
6298    Ok(updated)
6299}
6300
6301/// A permission's check, as a column of the write's `RETURNING`.
6302///
6303/// Empty where nothing has to be checked. Otherwise `, (<predicate>) AS ...`,
6304/// evaluated per written row against the row itself -- so a row the permission
6305/// refuses is known before the transaction is settled, and settling it is
6306/// already a rollback once an error is reported.
6307fn permission_check_sql(
6308    gql_ctx: &GraphQLContext,
6309    names: &crate::names::NameOverrides,
6310    schema_name: &str,
6311    table_name: &str,
6312    verb: crate::role::Verb,
6313    scope: &WhereScope<'_>,
6314    start_param_idx: usize,
6315) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6316    let check = crate::role::write_check(&gql_ctx.caller(), names, schema_name, table_name, verb)
6317        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6318    let Some(check) = check else {
6319        return Ok((String::new(), Vec::new()));
6320    };
6321
6322    let mut values = Vec::new();
6323    let mut param_idx = start_param_idx;
6324    let mut alias_counter = 0usize;
6325    let condition = build_condition(
6326        &check,
6327        scope,
6328        &mut param_idx,
6329        &mut values,
6330        &mut alias_counter,
6331    )?;
6332    Ok(match condition {
6333        // A check that compiles to nothing restricts nothing, which is what an
6334        // empty one says.
6335        None => (String::new(), Vec::new()),
6336        Some(sql) => (
6337            format!(
6338                ", ({}) AS {}",
6339                sql,
6340                postrust_sql::escape_ident(CHECK_COLUMN)
6341            ),
6342            values,
6343        ),
6344    })
6345}
6346
6347/// The column a permission's check is returned in.
6348const CHECK_COLUMN: &str = "pgrst_permission_check";
6349
6350/// Refuse the whole write if any row it produced fails the permission's check.
6351///
6352/// Every row, not the first: a mutation is one transaction and a single
6353/// refused row takes the rest with it, which is what makes a check a rule
6354/// about the write rather than about a row. A null counts as a failure -- an
6355/// unknown is not a satisfied condition.
6356fn refuse_unchecked_rows(
6357    rows: &[sqlx::postgres::PgRow],
6358    checked: bool,
6359) -> Result<(), async_graphql::Error> {
6360    if !checked {
6361        return Ok(());
6362    }
6363    use sqlx::Row;
6364    let all_passed = rows
6365        .iter()
6366        .all(|row| row.try_get::<bool, _>(CHECK_COLUMN).unwrap_or(false));
6367    match all_passed {
6368        true => Ok(()),
6369        false => Err(coded_error("permission-error", crate::role::CHECK_FAILED)),
6370    }
6371}
6372
6373/// Execute a delete mutation.
6374#[allow(clippy::too_many_arguments)] // the projection needs the schema it reads
6375async fn execute_delete(
6376    pool: &PgPool,
6377    gql_ctx: &GraphQLContext,
6378    schema_name: &str,
6379    table_name: &str,
6380    type_name: &str,
6381    where_clause: Option<serde_json::Value>,
6382    returning: Option<async_graphql::SelectionField<'_>>,
6383    relationships: &HashMap<String, Vec<RelationshipField>>,
6384    names: &crate::names::NameOverrides,
6385    max_rows: Option<i64>,
6386) -> Result<Vec<Value>, async_graphql::Error> {
6387    use sqlx::Row;
6388
6389    trace!("Delete mutation for {}", table_name);
6390
6391    let guard = gql_ctx
6392        .schema_cache
6393        .get()
6394        .await
6395        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6396    let cache = guard
6397        .as_ref()
6398        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6399    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
6400    let column_types = cache
6401        .get_table(&qi)
6402        .map(|table| exposed_column_types(table, names))
6403        .unwrap_or_default();
6404
6405    // Build WHERE clause. It can follow a relationship -- `delete_article(where:
6406    // {author: {id: {_eq: 2}}})` is the article-by-author delete anyone writes
6407    // -- so the scope is given the schema to resolve one with.
6408    let scope = WhereScope::table(schema_name, table_name, type_name, names)
6409        .under_alias(WRITTEN_ROW)
6410        .with_resolution(cache, relationships);
6411    let (where_sql, mut values) = build_where_clause(where_clause.as_ref(), 1, &scope)?;
6412
6413    // An absent or unrecognised `where` argument yields an empty clause, which
6414    // would delete every row in the table. Refuse instead.
6415    if where_sql.is_empty() {
6416        return Err(async_graphql::Error::new(format!(
6417            "delete on \"{}\" requires a `where` argument with at least one \
6418             recognised condition; refusing to delete every row",
6419            table_name
6420        )));
6421    }
6422
6423    // A relationship or computed field asked for in `returning` cannot be read
6424    // afterwards -- the rows are gone by then, which is why this used to answer
6425    // the plain columns and then fail on a non-null list. Inside one statement
6426    // the deleted rows are still a table: the delete goes in a CTE and the
6427    // projection reads from it, while the rows it points at are still there.
6428    let mut projection = String::new();
6429    let renamed = cache
6430        .get_table(&qi)
6431        .and_then(|table| rename_projection(table, "src", names));
6432    if let Some(returning) = returning {
6433        let mut param_idx = values.len() + 1;
6434        let mut alias_counter = 0usize;
6435        let embeds = build_embed_expressions(
6436            &gql_ctx.caller(),
6437            cache,
6438            relationships,
6439            type_name,
6440            "src",
6441            returning.selection_set(),
6442            max_rows,
6443            &mut alias_counter,
6444            &mut param_idx,
6445            &mut values,
6446            names,
6447        )?;
6448        let computed = match cache.get_table(&qi) {
6449            Some(table) => computed_projections(
6450                table,
6451                returning.selection_set(),
6452                "src",
6453                names,
6454                cache,
6455                &mut param_idx,
6456                &mut values,
6457            )?,
6458            None => Vec::new(),
6459        };
6460        for expression in &computed {
6461            projection.push_str(", ");
6462            projection.push_str(expression);
6463        }
6464        for (name, expression) in &embeds {
6465            projection.push_str(", ");
6466            projection.push_str(expression);
6467            projection.push_str(" AS ");
6468            projection.push_str(&postrust_sql::escape_ident(name));
6469        }
6470    }
6471
6472    let written = postrust_sql::escape_ident(WRITTEN_ROW);
6473    let sql = if projection.is_empty() && renamed.is_none() {
6474        format!(
6475            "DELETE FROM {}.{} AS {} {} RETURNING {}",
6476            postrust_sql::escape_ident(schema_name),
6477            postrust_sql::escape_ident(table_name),
6478            written,
6479            where_sql,
6480            row_json(&written, &column_types)
6481        )
6482    } else {
6483        format!(
6484            "WITH pgrst_deleted AS (DELETE FROM {}.{} AS {} {} RETURNING {}.*) \
6485             SELECT {} FROM (SELECT {}{} FROM pgrst_deleted AS src) AS pgrst_r",
6486            postrust_sql::escape_ident(schema_name),
6487            postrust_sql::escape_ident(table_name),
6488            written,
6489            where_sql,
6490            written,
6491            row_json("pgrst_r", &column_types),
6492            renamed.unwrap_or_else(|| "src.*".to_string()),
6493            projection
6494        )
6495    };
6496
6497    trace!("Executing DELETE SQL: {}", sql);
6498
6499    let mut query = sqlx::query(AssertSqlSafe(sql));
6500    for val in &values {
6501        query = bind_json_value(query, val);
6502    }
6503
6504    let mut guard = write_tx(gql_ctx, pool).await?;
6505    let conn = guard.as_mut().expect("write_tx opens one");
6506    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6507
6508    let deleted: Vec<Value> = rows
6509        .iter()
6510        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6511        .map(json_to_value)
6512        .collect();
6513
6514    Ok(deleted)
6515}
6516
6517/// The predicate a role's permission adds to every read of one table.
6518///
6519/// `None` where nothing restricts the read: no permission document, an
6520/// administrator, or a permission whose filter is `{}`. The value is a
6521/// `<table>_bool_exp` like any other, because [`crate::role::permission_where`]
6522/// rewrites it into one -- which is what lets the same compiler serve both and
6523/// keeps a permission able to say everything a `where` can.
6524fn permission_predicate(
6525    caller: &crate::role::Caller<'_>,
6526    names: &crate::names::NameOverrides,
6527    schema: &str,
6528    table: &str,
6529) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6530    permission_filter(caller, names, schema, table, crate::role::Verb::Select)
6531}
6532
6533/// The same, for whichever grant the operation is asking under.
6534fn permission_filter(
6535    caller: &crate::role::Caller<'_>,
6536    names: &crate::names::NameOverrides,
6537    schema: &str,
6538    table: &str,
6539    verb: crate::role::Verb,
6540) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6541    crate::role::row_filter(caller, names, schema, table, verb)
6542        .map(|filter| filter.map(|predicate| serde_json::json!({ PERMISSION: predicate })))
6543        .map_err(|fault| coded_error(fault.code(), fault.to_string()))
6544}
6545
6546/// The key a permission's own predicate is wrapped in on its way to the
6547/// compiler.
6548///
6549/// The two halves of a `where` are the same language and are compiled by the
6550/// same code, which is what keeps a permission able to say everything a client
6551/// can. They are not evaluated by the same party: a client's half asks what
6552/// *this caller* may see, and a permission's half is the server's own
6553/// predicate, evaluated as the server.
6554///
6555/// The difference shows on a relationship. `{"Artist": {"id":
6556/// "X-Hasura-Artist-Id"}}` on `Track` grants the tracks belonging to the
6557/// caller's artist -- to a role that has no permission on `Artist` at all, and
6558/// is not meant to have one: the predicate consults that table, it does not
6559/// expose it. Compiled as the caller, it needs a permission that does not
6560/// exist and refuses itself; compiled as the server, it says what it was
6561/// written to say. `_exists` has always worked this way and says so where it
6562/// is built; this is the same rule for the relationship a permission follows.
6563///
6564/// The name cannot collide with a column: no boolean expression a client can
6565/// write has this field, because no input type declares it.
6566const PERMISSION: &str = "_permission";
6567
6568/// How many rows a permission lets one read return.
6569///
6570/// A ceiling rather than a default: a request asking for more gets this, one
6571/// asking for fewer gets what it asked for, and one asking for nothing gets
6572/// the ceiling. The same rule `PGRST_MAX_ROWS` follows, folded with it.
6573fn permission_limit(
6574    caller: &crate::role::Caller<'_>,
6575    names: &crate::names::NameOverrides,
6576    schema: &str,
6577    table: &str,
6578) -> Option<i64> {
6579    crate::role::read_limit(caller, names, schema, table)
6580}
6581
6582/// Both predicates, as one.
6583///
6584/// Written as an `_and` rather than as two clauses so that everything
6585/// downstream -- parameter numbering, relationship resolution, the `EXISTS`
6586/// that a relationship predicate becomes -- happens once, in one place, for
6587/// both halves.
6588fn and_predicates(
6589    request: Option<serde_json::Value>,
6590    permission: Option<serde_json::Value>,
6591) -> Option<serde_json::Value> {
6592    match (request, permission) {
6593        (None, None) => None,
6594        (Some(only), None) | (None, Some(only)) => Some(only),
6595        (Some(request), Some(permission)) => {
6596            Some(serde_json::json!({ "_and": [request, permission] }))
6597        }
6598    }
6599}
6600
6601/// Build a WHERE clause from a boolean expression.
6602///
6603/// The expression is the JSON form of a `<table>_bool_exp`: column names
6604/// mapping to comparisons, and `_and`, `_or` and `_not` for structure. It
6605/// nests, so this recurses.
6606///
6607/// Nothing is dropped silently. An operator that is not recognised widens the
6608/// result set if it is ignored -- every row for a query, every row for a
6609/// mutation -- so it is an error instead.
6610fn build_where_clause(
6611    where_value: Option<&serde_json::Value>,
6612    start_param_idx: usize,
6613    scope: &WhereScope<'_>,
6614) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6615    let mut values: Vec<serde_json::Value> = Vec::new();
6616    let mut param_idx = start_param_idx;
6617    let mut alias_counter = 0usize;
6618
6619    let condition = match where_value {
6620        Some(value) => build_condition(
6621            value,
6622            scope,
6623            &mut param_idx,
6624            &mut values,
6625            &mut alias_counter,
6626        )?,
6627        None => None,
6628    };
6629
6630    Ok(match condition {
6631        Some(sql) => (format!("WHERE {}", sql), values),
6632        None => (String::new(), values),
6633    })
6634}
6635
6636/// The table a boolean expression is being read against.
6637///
6638/// Column references are qualified with `sql_ref` so that a predicate over a
6639/// relationship -- which becomes a correlated `EXISTS` against another table --
6640/// can tell the two apart. Without the qualification a child column sharing a
6641/// parent column's name would silently resolve to whichever PostgreSQL
6642/// preferred.
6643///
6644/// `resolution` is absent where a caller has no schema cache to hand, which is
6645/// how the mutation paths are called. A relationship predicate then reports
6646/// that it cannot be resolved rather than being read as a column.
6647pub struct WhereScope<'a> {
6648    /// The table itself, for the comparisons that need a column's type.
6649    qualified: postrust_core::api_request::QualifiedIdentifier,
6650    /// How to refer to this table's columns in SQL.
6651    sql_ref: String,
6652    /// How to refer to this table's *row*, which is what a function taking the
6653    /// row is passed. A qualified name reads as a column reference there, so
6654    /// this is the bare alias.
6655    row_ref: String,
6656    /// The GraphQL type name, which is the key into the relationship map.
6657    type_name: String,
6658    /// How to refer to the columns of the table the predicate started at, and
6659    /// which table that is.
6660    ///
6661    /// A column comparison written `["$", "name"]` means that table's column,
6662    /// however many relationships the predicate has descended through since --
6663    /// which is what makes the form worth having. Equal to this table's own
6664    /// until an `EXISTS` is entered.
6665    root_ref: String,
6666    root_qualified: postrust_core::api_request::QualifiedIdentifier,
6667    /// The names this table's columns are exposed under, so a predicate
6668    /// written against a field can be written as SQL against a column.
6669    names: &'a crate::names::NameOverrides,
6670    resolution: Option<WhereResolution<'a>>,
6671    /// Who is asking, so that a predicate following a relationship narrows the
6672    /// far side to what that caller may read.
6673    ///
6674    /// Unrestricted unless a scope is told otherwise, which is what leaves
6675    /// every path that has no caller to hand -- the mutation ones -- behaving
6676    /// as it did.
6677    caller: Option<crate::role::Caller<'a>>,
6678}
6679
6680/// What a scope needs to follow a relationship.
6681struct WhereResolution<'a> {
6682    cache: &'a SchemaCache,
6683    relationships: &'a HashMap<String, Vec<RelationshipField>>,
6684}
6685
6686impl<'a> WhereScope<'a> {
6687    /// A scope over a table addressed by its qualified name.
6688    pub fn table(
6689        schema: &str,
6690        table: &str,
6691        type_name: &str,
6692        names: &'a crate::names::NameOverrides,
6693    ) -> Self {
6694        Self {
6695            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6696            sql_ref: format!(
6697                "{}.{}",
6698                postrust_sql::escape_ident(schema),
6699                postrust_sql::escape_ident(table)
6700            ),
6701            row_ref: postrust_sql::escape_ident(table),
6702            type_name: type_name.to_string(),
6703            root_ref: format!(
6704                "{}.{}",
6705                postrust_sql::escape_ident(schema),
6706                postrust_sql::escape_ident(table)
6707            ),
6708            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6709            names,
6710            resolution: None,
6711            caller: None,
6712        }
6713    }
6714
6715    /// The same, referred to by an alias rather than by its name.
6716    ///
6717    /// A statement that writes a table has to name that table's row in
6718    /// `RETURNING`, and a bare table name there is read as a *column* first --
6719    /// so `area.area`, a geography column in a table of the same name, made
6720    /// `to_jsonb("area")` the shape rather than the row. The alias is a name
6721    /// no column has.
6722    fn under_alias(mut self, alias: &str) -> Self {
6723        // The root moves with it: this is the outermost table being renamed,
6724        // not a step into another one.
6725        if self.root_ref == self.sql_ref {
6726            self.root_ref = postrust_sql::escape_ident(alias);
6727        }
6728        self.sql_ref = postrust_sql::escape_ident(alias);
6729        self.row_ref = postrust_sql::escape_ident(alias);
6730        self
6731    }
6732
6733    /// The same, able to follow relationships.
6734    fn with_resolution(
6735        mut self,
6736        cache: &'a SchemaCache,
6737        relationships: &'a HashMap<String, Vec<RelationshipField>>,
6738    ) -> Self {
6739        self.resolution = Some(WhereResolution {
6740            cache,
6741            relationships,
6742        });
6743        self
6744    }
6745
6746    /// A scope over an aliased table, able to follow relationships.
6747    #[allow(clippy::too_many_arguments)]
6748    fn for_alias(
6749        schema: &str,
6750        table: &str,
6751        alias: &str,
6752        type_name: &str,
6753        cache: &'a SchemaCache,
6754        relationships: &'a HashMap<String, Vec<RelationshipField>>,
6755        names: &'a crate::names::NameOverrides,
6756    ) -> Self {
6757        Self {
6758            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6759            sql_ref: postrust_sql::escape_ident(alias),
6760            row_ref: postrust_sql::escape_ident(alias),
6761            type_name: type_name.to_string(),
6762            root_ref: postrust_sql::escape_ident(alias),
6763            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6764            names,
6765            resolution: Some(WhereResolution {
6766                cache,
6767                relationships,
6768            }),
6769            caller: None,
6770        }
6771    }
6772
6773    /// A scope over an aliased table, for the inside of an `EXISTS`.
6774    /// A scope over the *other* table of a relationship, under an alias.
6775    ///
6776    /// The child's own qualified name, not the parent's: a comparison inside
6777    /// an `EXISTS` is against the child's columns, so it is the child's types
6778    /// and the child's renames that decide how to write it.
6779    fn aliased(
6780        schema: &str,
6781        table: &str,
6782        alias: &str,
6783        type_name: &str,
6784        from: &WhereScope<'a>,
6785    ) -> Self {
6786        Self {
6787            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6788            sql_ref: postrust_sql::escape_ident(alias),
6789            row_ref: postrust_sql::escape_ident(alias),
6790            type_name: type_name.to_string(),
6791            // Carried, not replaced: `$` names the table the predicate began
6792            // at, which is the whole point of descending with it.
6793            root_ref: from.root_ref.clone(),
6794            root_qualified: from.root_qualified.clone(),
6795            names: from.names,
6796            resolution: from.resolution.as_ref().map(|r| WhereResolution {
6797                cache: r.cache,
6798                relationships: r.relationships,
6799            }),
6800            // Carried across the relationship: the caller does not change on
6801            // the way into an EXISTS, and the far side has its own permission
6802            // to be narrowed by.
6803            caller: from.caller,
6804        }
6805    }
6806
6807    /// Narrow what this scope's predicates may reach to one caller.
6808    pub fn for_caller(mut self, caller: crate::role::Caller<'a>) -> Self {
6809        self.caller = Some(caller);
6810        self
6811    }
6812
6813    /// The same scope with no caller: what a permission's own predicate is
6814    /// evaluated as. See `PERMISSION`.
6815    fn as_system(&self) -> WhereScope<'a> {
6816        WhereScope {
6817            qualified: self.qualified.clone(),
6818            sql_ref: self.sql_ref.clone(),
6819            row_ref: self.row_ref.clone(),
6820            type_name: self.type_name.clone(),
6821            // The root moves here. `$` in a permission names the table the
6822            // permission is *written on*, which is this one -- not whatever
6823            // table the query happened to start at. Reading `dml_rbp_tasks`
6824            // through a project's `where: {tasks: {...}}` compared the task's
6825            // grant against a column of `dml_rbp_projects` until this, and
6826            // answered with rows the role may not see.
6827            root_ref: self.sql_ref.clone(),
6828            root_qualified: self.qualified.clone(),
6829            names: self.names,
6830            resolution: self.resolution.as_ref().map(|r| WhereResolution {
6831                cache: r.cache,
6832                relationships: r.relationships,
6833            }),
6834            caller: None,
6835        }
6836    }
6837
6838    /// What a field name refers to, as SQL: a column of this table, or the
6839    /// call that produces a computed field.
6840    ///
6841    /// A computed field is in the boolean expression because it is on the type
6842    /// -- `where: {sum_float_offset: {_gt: 5}}` is a question a client can
6843    /// write -- and reading it as a column answered `column
6844    /// float_test.sum_float_offset does not exist`.
6845    fn column(&self, name: &str) -> String {
6846        let column = self.column_name(name);
6847        let plain = format!("{}.{}", self.sql_ref, postrust_sql::escape_ident(column));
6848        let Some(cache) = self.resolution.as_ref().map(|r| r.cache) else {
6849            return plain;
6850        };
6851        let Some(table) = cache.get_table(&self.qualified) else {
6852            return plain;
6853        };
6854        if table.get_column(column).is_some() {
6855            return plain;
6856        }
6857        let function = self
6858            .names
6859            .computed_source(&table.schema, &table.name, name)
6860            .unwrap_or(name);
6861        match table.get_computed_column(function) {
6862            Some(definition) => {
6863                computed_call(definition, &format!("{}.*", self.row_ref), &self.row_ref)
6864            }
6865            None => plain,
6866        }
6867    }
6868
6869    /// The column behind a field name.
6870    fn column_name<'n>(&'n self, name: &'n str) -> &'n str {
6871        self.names
6872            .column_source(&self.qualified.schema, &self.qualified.name, name)
6873            .unwrap_or(name)
6874    }
6875
6876    /// The PostgreSQL type of one of this table's columns, where it can be
6877    /// found. A spatial comparison needs it: the same function takes a
6878    /// geometry or a geography and the operand has to be cast to match.
6879    fn column_type(&self, name: &str) -> Option<String> {
6880        let cache = self.resolution.as_ref()?.cache;
6881        let table = cache.get_table(&self.qualified)?;
6882        table
6883            .get_column(self.column_name(name))
6884            .map(|c| c.nominal_type.clone())
6885    }
6886
6887    /// The other side of a column-to-column comparison, as SQL.
6888    ///
6889    /// Hasura writes the operand two ways. A bare name is a column of *this*
6890    /// table -- `{"bid_price": {"$cgt": "price"}}` compares two columns of one
6891    /// auction. `["$", "name"]` is a column of the table the permission is
6892    /// written on, which is what makes the form useful inside a predicate over
6893    /// a relationship: `{"item": {"item_quantity": {"_cgte": ["$",
6894    /// "order_cart_quantity"]}}}` asks whether the item has as many in stock
6895    /// as the cart is asking for, and the cart is the outer row.
6896    ///
6897    /// Nothing else is accepted. A longer path would walk relationships to
6898    /// reach the column, which Hasura allows and no case here writes -- and
6899    /// reading it wrongly would widen a permission rather than narrow it.
6900    fn other_column(
6901        &self,
6902        operand: &serde_json::Value,
6903        op: &str,
6904    ) -> Result<String, async_graphql::Error> {
6905        use serde_json::Value;
6906
6907        let refused = |what: &str| {
6908            async_graphql::Error::new(format!(
6909                "the \"{}\" comparison takes the name of a column, {}",
6910                op, what
6911            ))
6912        };
6913        let (against, qualified, name) = match operand {
6914            Value::String(name) => (&self.sql_ref, &self.qualified, name.as_str()),
6915            Value::Array(path) => match path.as_slice() {
6916                [Value::String(name)] => (&self.sql_ref, &self.qualified, name.as_str()),
6917                [Value::String(root), Value::String(name)] if root == "$" => {
6918                    (&self.root_ref, &self.root_qualified, name.as_str())
6919                }
6920                _ => return Err(refused("or [\"$\", \"a column\"]")),
6921            },
6922            _ => return Err(refused("written as a string")),
6923        };
6924        // Read against whichever table it belongs to, since a rename is
6925        // per-table and the two are not the same table.
6926        let column = self
6927            .names
6928            .column_source(&qualified.schema, &qualified.name, name)
6929            .unwrap_or(name);
6930        Ok(format!(
6931            "{}.{}",
6932            against,
6933            postrust_sql::escape_ident(column)
6934        ))
6935    }
6936
6937    /// The relationship a predicate is following, where there is one.
6938    ///
6939    /// Looked up in the schema's own map first, which is the answer for every
6940    /// relationship the role can read. A permission may name one it cannot:
6941    /// role `Artist` reads `Track` and filters it by `{"Artist": {"id": ...}}`
6942    /// while having no permission on `Artist` at all, so that relationship is
6943    /// in no schema this role is served. The catalogue still has it -- the
6944    /// cache a scope resolves against is the unreduced one -- and the
6945    /// relationship is derived from it there.
6946    ///
6947    /// Deriving it cannot let a client reach a hidden table: a key that is not
6948    /// a field of the boolean expression is refused before any of this runs,
6949    /// and the only writer of such a key is a permission.
6950    fn relationship(&self, name: &str) -> Option<std::borrow::Cow<'a, RelationshipField>> {
6951        let resolution = self.resolution.as_ref()?;
6952        let published = resolution
6953            .relationships
6954            .get(&self.type_name)
6955            .and_then(|fields| fields.iter().find(|r| r.name == name));
6956        if let Some(found) = published {
6957            return Some(std::borrow::Cow::Borrowed(found));
6958        }
6959
6960        let table = resolution.cache.get_table(&self.qualified)?;
6961
6962        // A relationship metadata declares, which may be a mapped join the
6963        // catalogue has never heard of. `message.members` is one, and a
6964        // permission on `message` is written through it -- so this is not a
6965        // convenience: without it that filter cannot be compiled at all and
6966        // the read is refused rather than narrowed.
6967        if let Some(declared) = self
6968            .names
6969            .declared_relationships(&table.schema, &table.name)
6970        {
6971            if let Some(entry) = declared.iter().find(|entry| entry.name == name) {
6972                // Reached by a mapping, which the catalogue has never heard
6973                // of.
6974                let target = entry
6975                    .target(&table.schema)
6976                    .map(|(_, target)| target)
6977                    .unwrap_or_default();
6978                if let Some(field) = crate::schema::mapped_relationship_field(
6979                    entry,
6980                    table,
6981                    resolution.cache,
6982                    &table.schema,
6983                    &target,
6984                    &target,
6985                    &target,
6986                ) {
6987                    return Some(std::borrow::Cow::Owned(field));
6988                }
6989                // Or by a key, under the name the declaration gives it --
6990                // which is the only place that name is written down now that
6991                // a declaration carries it.
6992                if let Some(key) = entry.using.as_deref() {
6993                    if let Some(found) = resolution
6994                        .cache
6995                        .get_relationships(&self.qualified, &table.schema)
6996                        .into_iter()
6997                        .flatten()
6998                        .find(|rel| {
6999                            crate::schema::relationship_keys(rel)
7000                                .iter()
7001                                .any(|found| found == key)
7002                        })
7003                    {
7004                        return Some(std::borrow::Cow::Owned(
7005                            crate::schema::relationship::RelationshipField::from_relationship_named(
7006                                found,
7007                                &found.foreign_table().name,
7008                                Some(&entry.name),
7009                            ),
7010                        ));
7011                    }
7012                }
7013            }
7014        }
7015
7016        resolution
7017            .cache
7018            .get_relationships(&self.qualified, &table.schema)?
7019            .iter()
7020            .find_map(|rel| {
7021                let given = crate::schema::relationship_keys(rel)
7022                    .iter()
7023                    .find_map(|key| self.names.relationship(&table.schema, &table.name, key));
7024                // The target's own name stands in for its GraphQL type, which
7025                // this role has none of. Nothing reads it but the lookup
7026                // above, and that lookup will land here again.
7027                let field = RelationshipField::from_relationship_named(
7028                    rel,
7029                    &rel.foreign_table().name,
7030                    given,
7031                );
7032                (field.name == name).then_some(std::borrow::Cow::Owned(field))
7033            })
7034    }
7035}
7036
7037/// The SQL operator behind a column-to-column comparison, where this is one.
7038///
7039/// `_ceq`, `_cne`, `_cgt`, `_cgte`, `_clt`, `_clte`: the `c` is for column.
7040/// Only a permission writes these -- no boolean expression a client can write
7041/// has them, because a client has no way to name the row a comparison would be
7042/// against. A permission does: `{"bid_price": {"$cgt": "price"}}` on `auction`
7043/// grants the rows whose bid has passed the reserve, which is a fact about the
7044/// row rather than about the caller.
7045fn column_comparison(op: &str) -> Option<&'static str> {
7046    Some(match op {
7047        "_ceq" => "=",
7048        "_cne" => "<>",
7049        "_cgt" => ">",
7050        "_cgte" => ">=",
7051        "_clt" => "<",
7052        "_clte" => "<=",
7053        _ => return None,
7054    })
7055}
7056
7057/// One boolean expression, as SQL. `None` where it constrains nothing.
7058fn build_condition(
7059    value: &serde_json::Value,
7060    scope: &WhereScope<'_>,
7061    param_idx: &mut usize,
7062    values: &mut Vec<serde_json::Value>,
7063    alias_counter: &mut usize,
7064) -> Result<Option<String>, async_graphql::Error> {
7065    let serde_json::Value::Object(map) = value else {
7066        return Ok(None);
7067    };
7068
7069    let mut conditions: Vec<String> = Vec::new();
7070
7071    for (key, val) in map {
7072        match key.as_str() {
7073            // An empty `_and` is true and an empty `_or` is false, which is
7074            // what SQL's own identities give: nothing to AND constrains
7075            // nothing, nothing to OR matches nothing.
7076            "_and" | "_or" => {
7077                let members = val.as_array().ok_or_else(|| {
7078                    async_graphql::Error::new(format!("\"{}\" takes a list of expressions", key))
7079                })?;
7080                let mut parts = Vec::new();
7081                for member in members {
7082                    if let Some(sql) =
7083                        build_condition(member, scope, param_idx, values, alias_counter)?
7084                    {
7085                        parts.push(sql);
7086                    }
7087                }
7088                if parts.is_empty() {
7089                    if key == "_or" {
7090                        conditions.push("false".to_string());
7091                    }
7092                    continue;
7093                }
7094                let joiner = if key == "_and" { " AND " } else { " OR " };
7095                conditions.push(format!("({})", parts.join(joiner)));
7096            }
7097            "_not" => {
7098                if let Some(sql) = build_condition(val, scope, param_idx, values, alias_counter)? {
7099                    conditions.push(format!("NOT ({})", sql));
7100                }
7101            }
7102            // A permission's own predicate, which is the server's rather
7103            // than the caller's. See `PERMISSION`.
7104            PERMISSION => {
7105                if let Some(sql) =
7106                    build_condition(val, &scope.as_system(), param_idx, values, alias_counter)?
7107                {
7108                    conditions.push(sql);
7109                }
7110            }
7111            // A question about a table nothing relates this one to. Only a
7112            // permission writes it; no client can, because no boolean
7113            // expression has the field.
7114            "_exists" => {
7115                conditions.push(table_exists_sql(
7116                    val,
7117                    scope,
7118                    param_idx,
7119                    values,
7120                    alias_counter,
7121                )?);
7122            }
7123            // A relationship is filtered by filtering the rows at its other
7124            // end. `where: {articles: {title: {_eq: "x"}}}` keeps the authors
7125            // that have such an article -- an `EXISTS` correlated back to the
7126            // parent row, which is also what it means for a to-one
7127            // relationship, where the subquery simply cannot match twice.
7128            name if scope.relationship(name).is_some() => {
7129                let relationship = scope.relationship(name).expect("just checked");
7130                conditions.push(exists_sql(
7131                    &relationship,
7132                    val,
7133                    scope,
7134                    param_idx,
7135                    values,
7136                    alias_counter,
7137                )?);
7138            }
7139            // A question about the whole related set rather than about any
7140            // one row of it. `where: {articles_aggregate: {count: {predicate:
7141            // {_gt: 2}}}}` keeps the authors with more than two articles,
7142            // which no `EXISTS` over one article can say.
7143            name if scope.column_type(name).is_none()
7144                && name
7145                    .strip_suffix("_aggregate")
7146                    .and_then(|rel| scope.relationship(rel))
7147                    .is_some_and(|rel| rel.is_list) =>
7148            {
7149                let relationship = name
7150                    .strip_suffix("_aggregate")
7151                    .and_then(|rel| scope.relationship(rel))
7152                    .expect("just checked");
7153                conditions.push(aggregate_predicate_sql(
7154                    &relationship,
7155                    val,
7156                    scope,
7157                    param_idx,
7158                    values,
7159                    alias_counter,
7160                )?);
7161            }
7162            column => {
7163                let quoted = scope.column(column);
7164                match val {
7165                    serde_json::Value::Object(ops) => {
7166                        let column_type = scope.column_type(column);
7167                        for (op, operand) in ops {
7168                            // A comparison against another column rather than
7169                            // against a value, which only a permission can
7170                            // write. It needs the scope to say *which* column,
7171                            // so it is answered here rather than in
7172                            // `comparison_sql`, which sees one side.
7173                            if let Some(other) = column_comparison(op) {
7174                                conditions.push(format!(
7175                                    "{} {} {}",
7176                                    quoted,
7177                                    other,
7178                                    scope.other_column(operand, op)?
7179                                ));
7180                                continue;
7181                            }
7182                            conditions.push(comparison_sql(
7183                                &quoted,
7184                                column,
7185                                column_type.as_deref(),
7186                                op,
7187                                operand,
7188                                param_idx,
7189                                values,
7190                            )?);
7191                        }
7192                    }
7193                    // `{id: 1}` rather than `{id: {_eq: 1}}`. Not a spelling
7194                    // Hasura accepts, but one that costs nothing to read and
7195                    // that hand-written queries reach for.
7196                    other => {
7197                        conditions.push(format!("{} = ${}", quoted, param_idx));
7198                        values.push(other.clone());
7199                        *param_idx += 1;
7200                    }
7201                }
7202            }
7203        }
7204    }
7205
7206    Ok(if conditions.is_empty() {
7207        None
7208    } else if conditions.len() == 1 {
7209        conditions.pop()
7210    } else {
7211        Some(conditions.join(" AND "))
7212    })
7213}
7214
7215/// A predicate over a relationship, as a correlated `EXISTS`.
7216///
7217/// Only a plain key join is expressible this way. A many-to-many through a
7218/// junction and a computed relationship both need more than a pair of columns
7219/// to correlate on, and saying so is the only honest answer -- reading the
7220/// predicate as though it constrained nothing would return every parent row.
7221fn exists_sql(
7222    relationship: &RelationshipField,
7223    child_expression: &serde_json::Value,
7224    scope: &WhereScope<'_>,
7225    param_idx: &mut usize,
7226    values: &mut Vec<serde_json::Value>,
7227    alias_counter: &mut usize,
7228) -> Result<String, async_graphql::Error> {
7229    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7230        async_graphql::Error::new(format!(
7231            "filtering on the relationship \"{}\" is not available here",
7232            relationship.name
7233        ))
7234    })?;
7235
7236    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7237        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7238
7239    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7240        return Err(async_graphql::Error::new(format!(
7241            "filtering on \"{}\" is not supported: it is reached through a junction \
7242             rather than by a key",
7243            relationship.name
7244        )));
7245    }
7246
7247    *alias_counter += 1;
7248    let alias = format!("pgrst_rel_{}", alias_counter);
7249    let child_scope = WhereScope::aliased(
7250        &plan.foreign_schema,
7251        &plan.foreign_table,
7252        &alias,
7253        &relationship.target_type,
7254        scope,
7255    );
7256
7257    // A computed relationship is correlated by argument -- the function takes
7258    // the parent row -- where a key relationship is correlated by columns.
7259    let (source, mut correlation) = match &plan.function {
7260        Some(function) => (
7261            format!(
7262                "{}.{}({})",
7263                postrust_sql::escape_ident(&function.schema),
7264                postrust_sql::escape_ident(&function.name),
7265                scope.row_ref
7266            ),
7267            Vec::new(),
7268        ),
7269        None => {
7270            let mut columns = Vec::with_capacity(plan.columns.len());
7271            for (parent_column, child_column) in &plan.columns {
7272                columns.push(format!(
7273                    "{} = {}",
7274                    scope.column(parent_column),
7275                    child_scope.column(child_column)
7276                ));
7277            }
7278            (
7279                format!(
7280                    "{}.{}",
7281                    postrust_sql::escape_ident(&plan.foreign_schema),
7282                    postrust_sql::escape_ident(&plan.foreign_table)
7283                ),
7284                columns,
7285            )
7286        }
7287    };
7288
7289    let child_condition = build_condition(
7290        child_expression,
7291        &child_scope,
7292        param_idx,
7293        values,
7294        alias_counter,
7295    )?;
7296    if let Some(sql) = child_condition {
7297        correlation.push(format!("({})", sql));
7298    }
7299
7300    // The far side's own permission. `where: {author: {name: {_eq: "Ann"}}}`
7301    // asks whether a related row exists, and a row this caller may not read is
7302    // one it may not learn the existence of either -- so the child's filter
7303    // narrows the EXISTS exactly as it narrows a read of the same table.
7304    if let Some(caller) = &child_scope.caller {
7305        // Through `permission_predicate` rather than `role::read_predicate`,
7306        // so the far side's filter is marked as the server's own -- see
7307        // `PERMISSION`. Without the mark it was compiled as the caller, and a
7308        // filter that follows a relationship of its own then needed a
7309        // permission on *that* table: `dml_rbp_tasks` is filtered through
7310        // `grants`, which the role reading tasks may not read.
7311        let permission = permission_predicate(
7312            caller,
7313            child_scope.names,
7314            &plan.foreign_schema,
7315            &plan.foreign_table,
7316        )?;
7317        if let Some(predicate) = permission {
7318            if let Some(sql) =
7319                build_condition(&predicate, &child_scope, param_idx, values, alias_counter)?
7320            {
7321                correlation.push(format!("({})", sql));
7322            }
7323        }
7324    }
7325    if correlation.is_empty() {
7326        correlation.push("true".to_string());
7327    }
7328
7329    Ok(format!(
7330        "EXISTS (SELECT 1 FROM {} AS {} WHERE {})",
7331        source,
7332        postrust_sql::escape_ident(&alias),
7333        correlation.join(" AND ")
7334    ))
7335}
7336
7337/// `_exists`, the predicate only a permission can write, as SQL.
7338///
7339/// ```json
7340/// {"_exists": {"_table": "user", "_where": {"id": "X-Hasura-User-Id",
7341///                                           "is_admin": true}}}
7342/// ```
7343///
7344/// It asks whether a row exists in *another* table -- one no foreign key
7345/// relates to this one. That is how Hasura writes "the caller is an
7346/// administrator" without asking the caller to say so: the row that decides it
7347/// lives in a table of its own, and whether the account this predicate guards
7348/// is readable does not depend on which account it is.
7349///
7350/// So the subselect is **uncorrelated**, which is the whole difference from
7351/// the `EXISTS` a relationship predicate builds. There are no key columns to
7352/// join on -- if there were, the permission would have been written as a
7353/// relationship -- and the `_where` is read against the named table's columns
7354/// alone.
7355///
7356/// The caller's own permissions are not applied inside it, and that is not an
7357/// oversight. The point of `_exists` is to consult a table the role has no
7358/// access to: in Hasura's corpus the role that reads `account` through this
7359/// predicate may not read `public.user` at all. Narrowing the subselect to
7360/// what the caller may read would make the predicate refuse itself.
7361fn table_exists_sql(
7362    spec: &serde_json::Value,
7363    scope: &WhereScope<'_>,
7364    param_idx: &mut usize,
7365    values: &mut Vec<serde_json::Value>,
7366    alias_counter: &mut usize,
7367) -> Result<String, async_graphql::Error> {
7368    let Some((schema, table)) = crate::role::exists_target(spec, &scope.qualified.schema) else {
7369        return Err(async_graphql::Error::new(
7370            "\"_exists\" needs a \"_table\" to look in",
7371        ));
7372    };
7373
7374    // The name comes from a permission document rather than from a request,
7375    // but it still reaches SQL, and a table that is not there would reach it as
7376    // a syntax error at the far end rather than as an answer here.
7377    let qualified = postrust_core::api_request::QualifiedIdentifier::new(&schema, &table);
7378    if scope
7379        .resolution
7380        .as_ref()
7381        .is_some_and(|r| r.cache.get_table(&qualified).is_none())
7382    {
7383        return Err(async_graphql::Error::new(format!(
7384            "\"_exists\" names a table this server does not have: \"{}.{}\"",
7385            schema, table
7386        )));
7387    }
7388
7389    *alias_counter += 1;
7390    let alias = format!("pgrst_exists_{}", alias_counter);
7391    // The type this table is exposed as, which is the key into the
7392    // relationship map -- so a `_where` may follow the named table's own
7393    // relationships, the way any other predicate over it could.
7394    let type_name = scope
7395        .names
7396        .base_name(&schema, &table)
7397        .unwrap_or(&table)
7398        .to_string();
7399    let mut inner = WhereScope::aliased(&schema, &table, &alias, &type_name, scope);
7400    // See above: the predicate is the permission, so it is not itself
7401    // subject to one.
7402    inner.caller = None;
7403
7404    let condition = match spec.get("_where") {
7405        Some(predicate) => build_condition(predicate, &inner, param_idx, values, alias_counter)?,
7406        None => None,
7407    };
7408
7409    Ok(format!(
7410        "EXISTS (SELECT 1 FROM {}.{} AS {} WHERE {})",
7411        postrust_sql::escape_ident(&schema),
7412        postrust_sql::escape_ident(&table),
7413        postrust_sql::escape_ident(&alias),
7414        condition.unwrap_or_else(|| "true".to_string())
7415    ))
7416}
7417
7418/// A predicate over an aggregate of a related set, as SQL.
7419///
7420/// `{count: {predicate: {_gt: 2}, filter: {...}}}` becomes a scalar subselect
7421/// correlated back to the parent row, compared the way any column is:
7422///
7423/// ```text
7424/// (SELECT count(*) FROM "public"."article" AS pgrst_rel_1
7425///   WHERE "author"."id" = pgrst_rel_1."author_id" AND (...)) > $1
7426/// ```
7427///
7428/// A scalar subselect rather than an `EXISTS`, because the answer is a number
7429/// or a truth value and not whether a row is there -- and because over no rows
7430/// at all `count` is zero and `bool_and` is null, which is what SQL says and
7431/// what a client asking "no articles" is relying on.
7432fn aggregate_predicate_sql(
7433    relationship: &RelationshipField,
7434    expression: &serde_json::Value,
7435    scope: &WhereScope<'_>,
7436    param_idx: &mut usize,
7437    values: &mut Vec<serde_json::Value>,
7438    alias_counter: &mut usize,
7439) -> Result<String, async_graphql::Error> {
7440    let serde_json::Value::Object(over) = expression else {
7441        return Ok("true".to_string());
7442    };
7443
7444    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7445        async_graphql::Error::new(format!(
7446            "filtering on the relationship \"{}\" is not available here",
7447            relationship.name
7448        ))
7449    })?;
7450    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7451        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7452    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7453        return Err(async_graphql::Error::new(format!(
7454            "aggregating over \"{}\" is not supported: it is reached through a junction \
7455             rather than by a key",
7456            relationship.name
7457        )));
7458    }
7459
7460    let mut conditions: Vec<String> = Vec::new();
7461    for (function, spec) in over {
7462        if spec.is_null() {
7463            continue;
7464        }
7465        let serde_json::Value::Object(spec) = spec else {
7466            continue;
7467        };
7468        let Some(predicate) = spec.get("predicate").filter(|p| !p.is_null()) else {
7469            return Err(validation_error(format!(
7470                "\"{}\" over \"{}\" needs a predicate",
7471                function, relationship.name
7472            )));
7473        };
7474
7475        *alias_counter += 1;
7476        let alias = format!("pgrst_agg_{}", alias_counter);
7477        let child_scope = WhereScope::aliased(
7478            &plan.foreign_schema,
7479            &plan.foreign_table,
7480            &alias,
7481            &relationship.target_type,
7482            scope,
7483        );
7484
7485        // Correlated the way an `EXISTS` over the same relationship is: by
7486        // the key, or by handing the parent row to the function.
7487        let (source, mut correlation) = match &plan.function {
7488            Some(routine) => (
7489                format!(
7490                    "{}.{}({})",
7491                    postrust_sql::escape_ident(&routine.schema),
7492                    postrust_sql::escape_ident(&routine.name),
7493                    scope.row_ref
7494                ),
7495                Vec::new(),
7496            ),
7497            None => {
7498                let mut columns = Vec::with_capacity(plan.columns.len());
7499                for (parent_column, child_column) in &plan.columns {
7500                    columns.push(format!(
7501                        "{} = {}",
7502                        scope.column(parent_column),
7503                        child_scope.column(child_column)
7504                    ));
7505                }
7506                (
7507                    format!(
7508                        "{}.{}",
7509                        postrust_sql::escape_ident(&plan.foreign_schema),
7510                        postrust_sql::escape_ident(&plan.foreign_table)
7511                    ),
7512                    columns,
7513                )
7514            }
7515        };
7516        // `filter` narrows what is aggregated, and is an ordinary boolean
7517        // expression over the child.
7518        if let Some(filter) = spec.get("filter").filter(|f| !f.is_null()) {
7519            if let Some(sql) =
7520                build_condition(filter, &child_scope, param_idx, values, alias_counter)?
7521            {
7522                correlation.push(format!("({})", sql));
7523            }
7524        }
7525        if correlation.is_empty() {
7526            correlation.push("true".to_string());
7527        }
7528
7529        let arguments = spec.get("arguments").filter(|a| !a.is_null());
7530        let distinct = matches!(spec.get("distinct"), Some(serde_json::Value::Bool(true)));
7531        let (aggregate, operand_type) = match function.as_str() {
7532            "count" => {
7533                let columns: Vec<String> = match arguments {
7534                    Some(serde_json::Value::Array(items)) => items
7535                        .iter()
7536                        .filter_map(|item| item.as_str())
7537                        .map(|column| child_scope.column(column))
7538                        .collect(),
7539                    Some(serde_json::Value::String(one)) => vec![child_scope.column(one)],
7540                    _ => Vec::new(),
7541                };
7542                let counted = match columns.len() {
7543                    0 => "*".to_string(),
7544                    1 => columns.into_iter().next().unwrap_or_default(),
7545                    _ => format!("({})", columns.join(", ")),
7546                };
7547                let counted = match distinct && counted != "*" {
7548                    true => format!("DISTINCT {}", counted),
7549                    false => counted,
7550                };
7551                (format!("count({})", counted), "integer")
7552            }
7553            "bool_and" | "bool_or" => {
7554                let Some(column) = arguments.and_then(|a| a.as_str()) else {
7555                    return Err(validation_error(format!(
7556                        "\"{}\" over \"{}\" needs the column to fold",
7557                        function, relationship.name
7558                    )));
7559                };
7560                (
7561                    format!("{}({})", function, child_scope.column(column)),
7562                    "boolean",
7563                )
7564            }
7565            other => {
7566                return Err(validation_error(format!(
7567                    "\"{}\" is not an aggregate a predicate can be written over",
7568                    other
7569                )))
7570            }
7571        };
7572
7573        let subselect = format!(
7574            "(SELECT {} FROM {} AS {} WHERE {})",
7575            aggregate,
7576            source,
7577            postrust_sql::escape_ident(&alias),
7578            correlation.join(" AND ")
7579        );
7580        let serde_json::Value::Object(operators) = predicate else {
7581            return Err(validation_error(format!(
7582                "the predicate on \"{}\" is not a comparison",
7583                function
7584            )));
7585        };
7586        for (operator, operand) in operators {
7587            conditions.push(comparison_sql(
7588                &subselect,
7589                function,
7590                Some(operand_type),
7591                operator,
7592                operand,
7593                param_idx,
7594                values,
7595            )?);
7596        }
7597    }
7598
7599    Ok(match conditions.is_empty() {
7600        true => "true".to_string(),
7601        false => format!("({})", conditions.join(" AND ")),
7602    })
7603}
7604
7605/// One spatial comparison, as a PostGIS call.
7606///
7607/// The operand arrives as GeoJSON, which is what Hasura accepts and what a
7608/// client sends, so it is parsed by `ST_GeomFromGeoJSON` rather than bound as
7609/// a shape. The cast that follows is the column's own type: the same function
7610/// takes a geometry or a geography and picking the wrong one is not an
7611/// overload PostGIS has.
7612#[allow(clippy::too_many_arguments)]
7613fn postgis_sql(
7614    quoted: &str,
7615    column_type: Option<&str>,
7616    function: &str,
7617    op: &str,
7618    operand: &serde_json::Value,
7619    param_idx: &mut usize,
7620    values: &mut Vec<serde_json::Value>,
7621) -> Result<String, async_graphql::Error> {
7622    let is_geography = column_type == Some("geography");
7623    let mut shape = |value: &serde_json::Value, param_idx: &mut usize| -> String {
7624        let placeholder = format!("${}", param_idx);
7625        *param_idx += 1;
7626        values.push(value.clone());
7627        match is_geography {
7628            true => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
7629            false => format!("ST_GeomFromGeoJSON({})", placeholder),
7630        }
7631    };
7632
7633    match op {
7634        // `{distance, from}`: the shape and how far from it.
7635        "_st_d_within" | "_st_3d_d_within" => {
7636            let serde_json::Value::Object(spec) = operand else {
7637                return Err(async_graphql::Error::new(format!(
7638                    "\"{}\" takes {{distance, from}}",
7639                    op
7640                )));
7641            };
7642            let Some(from) = spec.get("from") else {
7643                return Err(async_graphql::Error::new(format!(
7644                    "\"{}\" needs a shape to measure from",
7645                    op
7646                )));
7647            };
7648            let Some(distance) = spec.get("distance") else {
7649                return Err(async_graphql::Error::new(format!(
7650                    "\"{}\" needs a distance",
7651                    op
7652                )));
7653            };
7654            let from_sql = shape(from, param_idx);
7655            let distance_sql = format!("${}", param_idx);
7656            *param_idx += 1;
7657            values.push(distance.clone());
7658            // On a sphere the answer depends on which sphere: `ST_DWithin`
7659            // over geography measures on the spheroid by default and on a
7660            // perfect sphere when told not to, and the two disagree by enough
7661            // to change which rows come back. Geometry has no such argument.
7662            let spheroid = match (is_geography, spec.get("use_spheroid")) {
7663                (true, Some(use_spheroid)) if !use_spheroid.is_null() => {
7664                    let placeholder = format!("${}", param_idx);
7665                    *param_idx += 1;
7666                    values.push(use_spheroid.clone());
7667                    format!(", {}::boolean", placeholder)
7668                }
7669                _ => String::new(),
7670            };
7671            Ok(format!(
7672                "{}({}, {}, {}::float8{})",
7673                function, quoted, from_sql, distance_sql, spheroid
7674            ))
7675        }
7676        // A raster against another raster, bound as one rather than parsed.
7677        "_st_intersects_rast" => {
7678            let placeholder = format!("${}", param_idx);
7679            *param_idx += 1;
7680            values.push(operand.clone());
7681            Ok(format!("{}({}, {}::raster)", function, quoted, placeholder))
7682        }
7683        // A raster against a shape, in one band or in any.
7684        "_st_intersects_geom_nband" | "_st_intersects_nband_geom" => {
7685            let serde_json::Value::Object(spec) = operand else {
7686                return Err(async_graphql::Error::new(format!(
7687                    "\"{}\" takes an object",
7688                    op
7689                )));
7690            };
7691            let Some(geometry) = spec.get("geommin") else {
7692                return Err(async_graphql::Error::new(format!(
7693                    "\"{}\" needs a shape",
7694                    op
7695                )));
7696            };
7697            let band = spec.get("nband").filter(|v| !v.is_null());
7698            let geometry_sql = shape(geometry, param_idx);
7699            Ok(match band {
7700                None => format!("{}({}, {})", function, quoted, geometry_sql),
7701                Some(band) => {
7702                    let placeholder = format!("${}", param_idx);
7703                    *param_idx += 1;
7704                    values.push(band.clone());
7705                    // `ST_Intersects(raster, nband, geometry)` is the spelling
7706                    // with a band; the argument order is PostGIS's, not the
7707                    // input's.
7708                    format!(
7709                        "{}({}, {}::int, {})",
7710                        function, quoted, placeholder, geometry_sql
7711                    )
7712                }
7713            })
7714        }
7715        // Everything else is the relation between this shape and one other.
7716        _ => {
7717            let other = shape(operand, param_idx);
7718            Ok(format!("{}({}, {})", function, quoted, other))
7719        }
7720    }
7721}
7722
7723/// A list of values as an array PostgreSQL will read.
7724///
7725/// Bound one element at a time rather than as one parameter. A JSON array
7726/// arrives as the text `["a","b"]`, which is not an array literal -- PostgreSQL
7727/// answers `malformed array literal` and means it.
7728fn sql_array(
7729    items: &[serde_json::Value],
7730    cast: &str,
7731    param_idx: &mut usize,
7732    values: &mut Vec<serde_json::Value>,
7733) -> String {
7734    if items.is_empty() {
7735        return format!("ARRAY[]::{}", cast);
7736    }
7737    let placeholders: Vec<String> = items
7738        .iter()
7739        .map(|item| {
7740            let placeholder = format!("${}", param_idx);
7741            *param_idx += 1;
7742            values.push(item.clone());
7743            placeholder
7744        })
7745        .collect();
7746    format!("ARRAY[{}]::{}", placeholders.join(", "), cast)
7747}
7748
7749/// One comparison against one column.
7750#[allow(clippy::too_many_arguments)]
7751fn comparison_sql(
7752    quoted: &str,
7753    column: &str,
7754    column_type: Option<&str>,
7755    op: &str,
7756    operand: &serde_json::Value,
7757    param_idx: &mut usize,
7758    values: &mut Vec<serde_json::Value>,
7759) -> Result<String, async_graphql::Error> {
7760    // `_cast` is not a comparison but a change of what is being compared: the
7761    // column becomes another type and the comparisons inside it apply to that.
7762    // A geometry and a geography answer different questions about the same
7763    // shape -- one on a plane, one on a sphere -- and this is how a client asks
7764    // for the other without the schema carrying two columns.
7765    if op == "_cast" {
7766        let serde_json::Value::Object(casts) = operand else {
7767            return Err(async_graphql::Error::new(
7768                "_cast takes an object naming the type to compare as",
7769            ));
7770        };
7771        let mut conditions = Vec::new();
7772        for (target, comparisons) in casts {
7773            // The GraphQL name of what is being compared as, and the
7774            // PostgreSQL type behind it. `String` is the one that differs:
7775            // a document compared as text is compared as `text`.
7776            let pg_type = match target.as_str() {
7777                "geometry" | "geography" => target.as_str(),
7778                "String" => "text",
7779                _ => {
7780                    return Err(async_graphql::Error::new(format!(
7781                        "cannot compare \"{}\" as \"{}\"",
7782                        column, target
7783                    )))
7784                }
7785            };
7786            let serde_json::Value::Object(ops) = comparisons else {
7787                continue;
7788            };
7789            let cast = format!("{}::{}", quoted, pg_type);
7790            for (nested_op, nested_operand) in ops {
7791                conditions.push(comparison_sql(
7792                    &cast,
7793                    column,
7794                    Some(pg_type),
7795                    nested_op,
7796                    nested_operand,
7797                    param_idx,
7798                    values,
7799                )?);
7800            }
7801        }
7802        return Ok(match conditions.len() {
7803            0 => "true".to_string(),
7804            1 => conditions.pop().expect("just counted"),
7805            _ => format!("({})", conditions.join(" AND ")),
7806        });
7807    }
7808
7809    // A spatial relation is a function of two shapes rather than an operator
7810    // between them, so it is written before the operator table is consulted.
7811    if let Some(function) = crate::input::bool_exp::postgis_function(op) {
7812        return postgis_sql(
7813            quoted,
7814            column_type,
7815            function,
7816            op,
7817            operand,
7818            param_idx,
7819            values,
7820        );
7821    }
7822
7823    // A tree comparison is an operator, but one whose operand has to be cast:
7824    // `?` is "any of these labels" for an ltree and "has this key" for a
7825    // jsonb, and PostgreSQL tells them apart by the operand's type alone.
7826    if let Some((operator, cast)) = crate::input::bool_exp::ltree_operator(op) {
7827        if cast.ends_with("[]") {
7828            let items = operand.as_array().ok_or_else(|| {
7829                async_graphql::Error::new(format!(
7830                    "the \"{}\" comparison on \"{}\" takes a list",
7831                    op, column
7832                ))
7833            })?;
7834            return Ok(format!(
7835                "{} {} {}",
7836                quoted,
7837                operator,
7838                sql_array(items, cast, param_idx, values)
7839            ));
7840        }
7841        let placeholder = format!("${}", param_idx);
7842        *param_idx += 1;
7843        values.push(operand.clone());
7844        return Ok(format!("{} {} {}::{}", quoted, operator, placeholder, cast));
7845    }
7846
7847    // The path language is a query over the document rather than a comparison
7848    // against one, so it is a function of the column and the path: `_exists`
7849    // asks whether the path selects anything, `_match` whether the predicate
7850    // it ends in holds.
7851    if let Some(function) = match op {
7852        "_jsonb_path_exists" => Some("jsonb_path_exists"),
7853        "_jsonb_path_match" => Some("jsonb_path_match"),
7854        _ => None,
7855    } {
7856        let placeholder = format!("${}", param_idx);
7857        *param_idx += 1;
7858        values.push(operand.clone());
7859        return Ok(format!(
7860            "{}({}, {}::jsonpath)",
7861            function, quoted, placeholder
7862        ));
7863    }
7864
7865    // Comparisons binding exactly one parameter, by the SQL they become.
7866    let binary = match op {
7867        "_eq" => Some("="),
7868        "_neq" => Some("<>"),
7869        "_gt" => Some(">"),
7870        "_gte" => Some(">="),
7871        "_lt" => Some("<"),
7872        "_lte" => Some("<="),
7873        "_like" => Some("LIKE"),
7874        "_nlike" => Some("NOT LIKE"),
7875        "_ilike" => Some("ILIKE"),
7876        "_nilike" => Some("NOT ILIKE"),
7877        "_similar" => Some("SIMILAR TO"),
7878        "_nsimilar" => Some("NOT SIMILAR TO"),
7879        "_regex" => Some("~"),
7880        "_nregex" => Some("!~"),
7881        "_iregex" => Some("~*"),
7882        "_niregex" => Some("!~*"),
7883        "_contains" => Some("@>"),
7884        "_contained_in" => Some("<@"),
7885        "_has_key" => Some("?"),
7886        _ => None,
7887    };
7888
7889    if let Some(sql_op) = binary {
7890        let placeholder = format!("${}", param_idx);
7891        *param_idx += 1;
7892        // A containment operand is a whole document, and `"latest"` is one --
7893        // a JSON string, which is not the same text as `latest`. Binding the
7894        // bare string answered `invalid input syntax for type json`, so what
7895        // goes over the wire is the value as it is written in JSON.
7896        values.push(match op {
7897            "_contains" | "_contained_in" => match operand {
7898                serde_json::Value::String(_)
7899                | serde_json::Value::Number(_)
7900                | serde_json::Value::Bool(_) => serde_json::Value::String(operand.to_string()),
7901                other => other.clone(),
7902            },
7903            _ => operand.clone(),
7904        });
7905        // A bound parameter arrives as text and PostgreSQL infers a type for
7906        // it from the operator, which works only while the inference is
7907        // unambiguous. `jsonb @> text` is not an operator at all; and `id =
7908        // $1` against an integer column answers `operator does not exist:
7909        // integer = text` the moment the value is one PostgreSQL cannot read
7910        // as a number -- a null, most obviously, which is how a client asks a
7911        // question it expects an empty answer to.
7912        //
7913        // So the operand says what it is. The containment and key operators
7914        // name the type the operator needs; everything else names the
7915        // column's, which is the type it is being compared against.
7916        let cast = match op {
7917            "_contains" | "_contained_in" => "::jsonb".to_string(),
7918            "_has_key" => "::text".to_string(),
7919            // Only where inference would otherwise pick text and be wrong. A
7920            // cast tells PostgreSQL what the *result* is, and it infers the
7921            // parameter feeding it as text -- so `$1::int4` bound with a
7922            // number sends binary int8 bytes into a text parameter and
7923            // answers `invalid byte sequence for encoding "UTF8"`. A value
7924            // PostgreSQL can read as a number needs no help; a null is the one
7925            // that does, because `id = $1` with nothing to infer from is
7926            // `integer = text`.
7927            _ if operand.is_null() => match column_type {
7928                Some(pg_type) => format!("::{}", pg_type),
7929                None => String::new(),
7930            },
7931            // A parameter arrives as `text`, and PostgreSQL then resolves the
7932            // operator by taking the *column* down to text where it can --
7933            // which is the wrong operator, and sometimes no operator at all.
7934            // `citext = text` compares case-sensitively against a
7935            // case-insensitive column; `uuid = text` does not exist. So a
7936            // written value names the type it is being compared against.
7937            //
7938            // Only a string: a number bound with a cast is sent as binary into
7939            // a parameter PostgreSQL has inferred as text, which is the
7940            // `invalid byte sequence for encoding "UTF8"` this used to answer.
7941            _ if operand.is_string() => match column_type {
7942                Some(pg_type) => format!("::{}", pg_type),
7943                None => String::new(),
7944            },
7945            _ => String::new(),
7946        };
7947        return Ok(format!("{} {} {}{}", quoted, sql_op, placeholder, cast));
7948    }
7949
7950    match op {
7951        "_is_null" => Ok(if operand.as_bool().unwrap_or(false) {
7952            format!("{} IS NULL", quoted)
7953        } else {
7954            format!("{} IS NOT NULL", quoted)
7955        }),
7956        "_in" | "_nin" => {
7957            let items = operand.as_array().ok_or_else(|| {
7958                async_graphql::Error::new(format!(
7959                    "the \"{}\" comparison on \"{}\" requires a list of values",
7960                    op, column
7961                ))
7962            })?;
7963            if items.is_empty() {
7964                // `IN ()` is not valid SQL. An empty `_in` matches nothing and
7965                // an empty `_nin` matches everything.
7966                return Ok(if op == "_in" { "false" } else { "true" }.to_string());
7967            }
7968            let mut placeholders = Vec::with_capacity(items.len());
7969            for item in items {
7970                // Cast only a null, for the reason the binary comparisons
7971                // give: a cast makes PostgreSQL infer the parameter as text.
7972                let cast = match (item.is_null() || item.is_string(), column_type) {
7973                    (true, Some(pg_type)) => format!("::{}", pg_type),
7974                    _ => String::new(),
7975                };
7976                placeholders.push(format!("${}{}", param_idx, cast));
7977                values.push(item.clone());
7978                *param_idx += 1;
7979            }
7980            Ok(format!(
7981                "{} {} ({})",
7982                quoted,
7983                if op == "_in" { "IN" } else { "NOT IN" },
7984                placeholders.join(", ")
7985            ))
7986        }
7987        "_has_keys_any" | "_has_keys_all" => {
7988            let items = operand.as_array().ok_or_else(|| {
7989                async_graphql::Error::new(format!(
7990                    "the \"{}\" comparison on \"{}\" requires a list of keys",
7991                    op, column
7992                ))
7993            })?;
7994            Ok(format!(
7995                "{} {} {}",
7996                quoted,
7997                if op == "_has_keys_any" { "?|" } else { "?&" },
7998                sql_array(items, "text[]", param_idx, values)
7999            ))
8000        }
8001        other => Err(async_graphql::Error::new(format!(
8002            "unsupported comparison \"{}\" on \"{}\"",
8003            other, column
8004        ))),
8005    }
8006}
8007
8008/// Build an `ORDER BY` clause from the `order_by` argument.
8009///
8010/// The argument is a list of single-column objects -- `[{name: asc}, {id:
8011/// desc}]` -- and the list is ordered, which is why it is a list and not one
8012/// object with several keys. Column names are checked against the table in the
8013/// schema cache before being quoted, so a name that is unknown, or crafted to
8014/// inject SQL, is rejected rather than interpolated. Returns an empty string
8015/// when no ordering was requested.
8016#[allow(clippy::too_many_arguments)] // one parameter per SQL clause
8017async fn build_order_by_clause(
8018    ctx: &ResolverContext<'_>,
8019    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8020    schema_name: &str,
8021    table_name: &str,
8022    type_name: &str,
8023    relationships: &HashMap<String, Vec<RelationshipField>>,
8024    reference: &str,
8025    names: &crate::names::NameOverrides,
8026) -> Result<String, async_graphql::Error> {
8027    let Ok(order_arg) = ctx.args.try_get("order_by") else {
8028        return Ok(String::new());
8029    };
8030
8031    let value = accessor_to_json(&order_arg);
8032    // A single object is accepted as a one-entry list, which is what a client
8033    // writing `order_by: {name: asc}` means and what Hasura reads it as.
8034    let entries: Vec<&serde_json::Value> = match &value {
8035        serde_json::Value::Array(items) => items.iter().collect(),
8036        serde_json::Value::Object(_) => vec![&value],
8037        serde_json::Value::Null => return Ok(String::new()),
8038        _ => {
8039            return Err(async_graphql::Error::new(
8040                "order_by takes an object or a list of them",
8041            ))
8042        }
8043    };
8044    if entries.is_empty() {
8045        return Ok(String::new());
8046    }
8047
8048    let guard = schema_cache
8049        .get()
8050        .await
8051        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8052    let cache = guard
8053        .as_ref()
8054        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8055    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8056    let table = cache
8057        .get_table(&qi)
8058        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8059
8060    let mut terms = Vec::new();
8061    let mut alias_counter = 0usize;
8062    for entry in entries {
8063        order_terms_into(
8064            entry,
8065            cache,
8066            relationships,
8067            type_name,
8068            table,
8069            reference,
8070            names,
8071            &mut alias_counter,
8072            &mut terms,
8073        )?;
8074    }
8075
8076    if terms.is_empty() {
8077        return Ok(String::new());
8078    }
8079    Ok(format!(" ORDER BY {}", terms.join(", ")))
8080}
8081
8082/// Collect the `ORDER BY` terms one entry contributes.
8083///
8084/// A key whose value is a direction is a column of this table. A key whose
8085/// value is an object is something the row points at, and ordering by it is a
8086/// correlated subselect: one related row contributes its column, and many
8087/// contribute an aggregate. That is the whole difference between the two
8088/// sides, and PostgreSQL will take a scalar subquery anywhere a column goes.
8089#[allow(clippy::too_many_arguments)]
8090fn order_terms_into(
8091    entry: &serde_json::Value,
8092    cache: &SchemaCache,
8093    relationships: &HashMap<String, Vec<RelationshipField>>,
8094    type_name: &str,
8095    table: &postrust_core::schema_cache::Table,
8096    reference: &str,
8097    names: &crate::names::NameOverrides,
8098    alias_counter: &mut usize,
8099    terms: &mut Vec<String>,
8100) -> Result<(), async_graphql::Error> {
8101    let serde_json::Value::Object(map) = entry else {
8102        return Err(async_graphql::Error::new(
8103            "each order_by entry is an object mapping a column to a direction",
8104        ));
8105    };
8106    let available: &[RelationshipField] = relationships
8107        .get(type_name)
8108        .map(|r| r.as_slice())
8109        .unwrap_or(&[]);
8110
8111    for (key, value) in map {
8112        // A direction given as null is no direction. `order_by: {id:
8113        // $direction}` with nothing bound for `$direction` is how a client
8114        // makes an ordering optional without writing the query twice, and the
8115        // same goes for `{author: $author_order_by}`, which is a whole
8116        // ordering left out.
8117        if value.is_null() {
8118            continue;
8119        }
8120
8121        // A direction: this table's own column, or a function of its row.
8122        if let Some(name) = value.as_str() {
8123            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8124                async_graphql::Error::new(format!(
8125                    "\"{}\" is not a sort direction; expected one of asc, desc, \
8126                     asc_nulls_first, asc_nulls_last, desc_nulls_first, desc_nulls_last",
8127                    name
8128                ))
8129            })?;
8130            let column = table_column_for(names, table, key);
8131            if table.get_column(column).is_some() {
8132                terms.push(format!(
8133                    "{}.{} {}",
8134                    reference,
8135                    postrust_sql::escape_ident(column),
8136                    sql
8137                ));
8138                continue;
8139            }
8140            // A computed field is a column as far as ordering is concerned:
8141            // the row is the argument, and the call is what is sorted on.
8142            let function = names
8143                .computed_source(&table.schema, &table.name, key)
8144                .unwrap_or(key);
8145            if let Some(definition) = table.get_computed_column(function) {
8146                terms.push(format!(
8147                    "{} {}",
8148                    computed_call(definition, &format!("{}.*", reference), reference),
8149                    sql
8150                ));
8151                continue;
8152            }
8153            return Err(async_graphql::Error::new(format!(
8154                "cannot order by unknown column \"{}\" on \"{}\"",
8155                key, table.name
8156            )));
8157        }
8158
8159        // An aggregate of the rows that point here.
8160        if let Some(rel) = key
8161            .strip_suffix("_aggregate")
8162            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list))
8163        {
8164            aggregate_order_terms(value, rel, cache, reference, alias_counter, terms)?;
8165            continue;
8166        }
8167
8168        // A column of the row this one points at.
8169        if let Some(rel) = available.iter().find(|r| r.name == *key && !r.is_list) {
8170            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8171                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8172            if plan.columns.is_empty() {
8173                return Err(async_graphql::Error::new(format!(
8174                    "cannot order by \"{}\": it is not reached by a key",
8175                    key
8176                )));
8177            }
8178            *alias_counter += 1;
8179            let alias = format!("pgrst_ord_{}", alias_counter);
8180            let quoted_alias = postrust_sql::escape_ident(&alias);
8181            let correlation = plan
8182                .columns
8183                .iter()
8184                .map(|(local, foreign)| {
8185                    format!(
8186                        "{} = {}.{}",
8187                        format_args!("{}.{}", reference, postrust_sql::escape_ident(local)),
8188                        quoted_alias,
8189                        postrust_sql::escape_ident(foreign)
8190                    )
8191                })
8192                .collect::<Vec<_>>()
8193                .join(" AND ");
8194
8195            let target_qi = postrust_core::api_request::QualifiedIdentifier::new(
8196                &plan.foreign_schema,
8197                &plan.foreign_table,
8198            );
8199            let Some(target) = cache.get_table(&target_qi) else {
8200                return Err(async_graphql::Error::new(format!(
8201                    "unknown table \"{}\"",
8202                    plan.foreign_table
8203                )));
8204            };
8205
8206            // The related row's own terms, then wrapped one at a time: a
8207            // subquery yields one value, so each term is its own subselect.
8208            let mut nested = Vec::new();
8209            order_terms_into(
8210                value,
8211                cache,
8212                relationships,
8213                &rel.target_type,
8214                target,
8215                &quoted_alias,
8216                names,
8217                alias_counter,
8218                &mut nested,
8219            )?;
8220            for term in nested {
8221                let (expression, direction) = term
8222                    .rsplit_once(' ')
8223                    .map(|(e, d)| (e.to_string(), d.to_string()))
8224                    .unwrap_or((term.clone(), String::new()));
8225                terms.push(format!(
8226                    "(SELECT {} FROM {}.{} AS {} WHERE {}) {}",
8227                    expression,
8228                    postrust_sql::escape_ident(&plan.foreign_schema),
8229                    postrust_sql::escape_ident(&plan.foreign_table),
8230                    quoted_alias,
8231                    correlation,
8232                    direction
8233                ));
8234            }
8235            continue;
8236        }
8237
8238        return Err(async_graphql::Error::new(format!(
8239            "cannot order by \"{}\" on \"{}\"",
8240            key, table.name
8241        )));
8242    }
8243    Ok(())
8244}
8245
8246/// The `ORDER BY` terms for an aggregate of a row's children.
8247fn aggregate_order_terms(
8248    value: &serde_json::Value,
8249    rel: &RelationshipField,
8250    cache: &SchemaCache,
8251    reference: &str,
8252    alias_counter: &mut usize,
8253    terms: &mut Vec<String>,
8254) -> Result<(), async_graphql::Error> {
8255    let serde_json::Value::Object(spec) = value else {
8256        return Err(async_graphql::Error::new(
8257            "ordering by an aggregate takes an object, such as {count: desc}",
8258        ));
8259    };
8260    let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8261        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8262    if plan.columns.is_empty() && plan.function.is_none() {
8263        return Err(async_graphql::Error::new(format!(
8264            "cannot order by an aggregate of \"{}\": it is not reached by a key",
8265            rel.name
8266        )));
8267    }
8268    // A computed relationship whose function takes more than the row cannot be
8269    // ordered by here: there is nowhere in `order_by` to write the arguments.
8270    if plan.function.is_some() && !rel.arguments.is_empty() {
8271        return Err(async_graphql::Error::new(format!(
8272            "cannot order by an aggregate of \"{}\": it takes arguments, and an \
8273             ordering has nowhere to write them",
8274            rel.name
8275        )));
8276    }
8277
8278    *alias_counter += 1;
8279    let alias = postrust_sql::escape_ident(&format!("pgrst_ord_{}", alias_counter));
8280    // A computed relationship is correlated by argument -- the function takes
8281    // the parent row -- where a key relationship is correlated by columns.
8282    let (source, correlation) = match &plan.function {
8283        Some(function) => {
8284            let session = plan.row_argument.as_ref().and_then(|row_argument| {
8285                computed_session_argument(cache, function, row_argument)
8286                    .map(|session| (row_argument.clone(), session))
8287            });
8288            let call = match session {
8289                Some((row_argument, session)) => format!(
8290                    "{} => {}, {} => {}",
8291                    postrust_sql::escape_ident(&row_argument),
8292                    reference,
8293                    postrust_sql::escape_ident(&session),
8294                    SESSION_ARGUMENT
8295                ),
8296                None => reference.to_string(),
8297            };
8298            (
8299                format!(
8300                    "{}.{}({})",
8301                    postrust_sql::escape_ident(&function.schema),
8302                    postrust_sql::escape_ident(&function.name),
8303                    call
8304                ),
8305                "true".to_string(),
8306            )
8307        }
8308        None => (
8309            format!(
8310                "{}.{}",
8311                postrust_sql::escape_ident(&plan.foreign_schema),
8312                postrust_sql::escape_ident(&plan.foreign_table)
8313            ),
8314            plan.columns
8315                .iter()
8316                .map(|(local, foreign)| {
8317                    format!(
8318                        "{}.{} = {}.{}",
8319                        reference,
8320                        postrust_sql::escape_ident(local),
8321                        alias,
8322                        postrust_sql::escape_ident(foreign)
8323                    )
8324                })
8325                .collect::<Vec<_>>()
8326                .join(" AND "),
8327        ),
8328    };
8329
8330    // `{count: desc}` is one term; `{max: {id: desc}}` is one per column.
8331    let mut wanted: Vec<(String, String)> = Vec::new();
8332    for (function, argument) in spec {
8333        // A direction given as null is no direction, as everywhere else an
8334        // ordering is read.
8335        if argument.is_null() {
8336            continue;
8337        }
8338        if function == "count" {
8339            let name = argument.as_str().unwrap_or_default();
8340            let Some(direction) = crate::input::order_by::direction_sql(name) else {
8341                return Err(async_graphql::Error::new(format!(
8342                    "\"{}\" is not a sort direction",
8343                    name
8344                )));
8345            };
8346            wanted.push(("count(*)".to_string(), direction.to_string()));
8347            continue;
8348        }
8349        let serde_json::Value::Object(columns) = argument else {
8350            continue;
8351        };
8352        for (column, direction) in columns {
8353            if direction.is_null() {
8354                continue;
8355            }
8356            let name = direction.as_str().unwrap_or_default();
8357            let Some(direction) = crate::input::order_by::direction_sql(name) else {
8358                return Err(async_graphql::Error::new(format!(
8359                    "\"{}\" is not a sort direction",
8360                    name
8361                )));
8362            };
8363            // The function comes from the generated input, not from the
8364            // request: a client can only name one that was offered.
8365            wanted.push((
8366                format!(
8367                    "{}({}.{})",
8368                    function,
8369                    alias,
8370                    postrust_sql::escape_ident(column)
8371                ),
8372                direction.to_string(),
8373            ));
8374        }
8375    }
8376
8377    for (expression, direction) in wanted {
8378        terms.push(format!(
8379            "(SELECT {} FROM {} AS {} WHERE {}) {}",
8380            expression, source, alias, correlation, direction
8381        ));
8382    }
8383    Ok(())
8384}
8385
8386/// The `DISTINCT ON (...)` prefix, and the ordering that has to go with it.
8387///
8388/// PostgreSQL keeps the first row of each DISTINCT ON group in the query's own
8389/// order, and picks arbitrarily where the ordering does not begin with the
8390/// distinct columns -- so which row survives would depend on the plan. Hasura
8391/// refuses that query rather than answering it, and so does this: prepending
8392/// the distinct columns instead produced an answer, and a wrong one, since
8393/// `ORDER BY "department", "department" DESC` is decided by its first term and
8394/// sorts ascending. Where nothing was ordered at all there is nothing to
8395/// disagree with, so the distinct columns become the ordering.
8396///
8397/// `written` is the ordering as its comma-separated terms, without the
8398/// keyword; the answer is in the same spelling.
8399fn distinct_on_clause(
8400    distinct_on: &[String],
8401    written: Option<&str>,
8402) -> Result<(String, Option<String>), async_graphql::Error> {
8403    if distinct_on.is_empty() {
8404        return Ok((String::new(), written.map(str::to_string)));
8405    }
8406    let terms: Vec<&str> = written
8407        .map(|written| written.split(", ").collect())
8408        .unwrap_or_default();
8409    // Terms are qualified and the distinct columns may not be, so they are
8410    // compared by the name itself.
8411    let column_of = |term: &str| {
8412        let expression = term.split_whitespace().next().unwrap_or(term);
8413        expression
8414            .rsplit_once('.')
8415            .map(|(_, name)| name)
8416            .unwrap_or(expression)
8417            .to_string()
8418    };
8419    let leading: Vec<String> = terms
8420        .iter()
8421        .take(distinct_on.len())
8422        .map(|term| column_of(term))
8423        .collect();
8424    if !terms.is_empty()
8425        && (leading.len() != distinct_on.len()
8426            || distinct_on
8427                .iter()
8428                .any(|column| !leading.contains(&column_of(column))))
8429    {
8430        return Err(validation_error(
8431            "\"distinct_on\" columns must match initial \"order_by\" columns",
8432        ));
8433    }
8434    let order = match terms.is_empty() {
8435        true => Some(distinct_on.join(", ")),
8436        false => written.map(str::to_string),
8437    };
8438    Ok((format!("DISTINCT ON ({}) ", distinct_on.join(", ")), order))
8439}
8440
8441/// Build the `DISTINCT ON (...)` prefix from the `distinct_on` argument.
8442///
8443/// PostgreSQL requires the leftmost `ORDER BY` terms to match the distinct
8444/// columns, and picks an arbitrary surviving row otherwise. The caller
8445/// prepends these terms to whatever ordering was asked for rather than
8446/// replacing it, so `distinct_on: [name]` with `order_by: [{id: desc}]` keeps
8447/// the highest id per name instead of an unpredictable one.
8448async fn build_distinct_on(
8449    ctx: &ResolverContext<'_>,
8450    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8451    schema_name: &str,
8452    table_name: &str,
8453    names: &crate::names::NameOverrides,
8454) -> Result<Vec<String>, async_graphql::Error> {
8455    let Ok(arg) = ctx.args.try_get("distinct_on") else {
8456        return Ok(Vec::new());
8457    };
8458    let value = accessor_to_json(&arg);
8459    let requested: Vec<String> = match &value {
8460        serde_json::Value::Array(items) => items
8461            .iter()
8462            .filter_map(|i| i.as_str().map(|s| s.to_string()))
8463            .collect(),
8464        serde_json::Value::String(one) => vec![one.clone()],
8465        _ => Vec::new(),
8466    };
8467    if requested.is_empty() {
8468        return Ok(Vec::new());
8469    }
8470
8471    let guard = schema_cache
8472        .get()
8473        .await
8474        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8475    let cache = guard
8476        .as_ref()
8477        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8478    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8479    let table = cache
8480        .get_table(&qi)
8481        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8482
8483    let mut quoted = Vec::with_capacity(requested.len());
8484    for field in requested {
8485        let name = table_column_for(names, table, &field).to_string();
8486        if table.get_column(&name).is_none() {
8487            return Err(async_graphql::Error::new(format!(
8488                "cannot take distinct on unknown column \"{}\" of \"{}\"",
8489                field, table_name
8490            )));
8491        }
8492        quoted.push(postrust_sql::escape_ident(&name));
8493    }
8494    Ok(quoted)
8495}
8496
8497/// Build the SELECT-list expressions that embed relationships in one query.
8498///
8499/// The GraphQL mirror of the REST builder: each requested relationship becomes a
8500/// correlated subselect yielding JSON, so the whole selection comes back from
8501/// the parent query instead of one query per relationship per level.
8502/// The SELECT-list entries for any computed columns the selection asks for.
8503///
8504/// A computed column is a function of one argument of the table's own row
8505/// type, so it is not in `*` and has to be named. PostgreSQL's functional
8506/// notation reads `upper_name(author.*)` and `author.upper_name` as the same
8507/// call; the explicit form is written here because it says which function is
8508/// being called.
8509#[allow(clippy::too_many_arguments)] // the fields, their source, and the binding state
8510fn computed_projections<'a>(
8511    table: &postrust_core::schema_cache::Table,
8512    fields: impl IntoIterator<Item = SelectionField<'a>>,
8513    row_reference: &str,
8514    names: &crate::names::NameOverrides,
8515    schema_cache: &SchemaCache,
8516    param_idx: &mut usize,
8517    values: &mut Vec<serde_json::Value>,
8518) -> Result<Vec<String>, async_graphql::Error> {
8519    let mut projections = Vec::new();
8520    for field in fields {
8521        let name = field.name();
8522        // A real column wins, and the projection already carries it -- under
8523        // this name, whether or not that is the column's own.
8524        if table
8525            .get_column(table_column_for(names, table, name))
8526            .is_some()
8527        {
8528            continue;
8529        }
8530        // The field may be exposed under a name that was given rather than
8531        // the function's own, so the call is looked up from either side.
8532        let function = names
8533            .computed_source(&table.schema, &table.name, name)
8534            .unwrap_or(name);
8535        let Some(computed) = table.get_computed_column(function) else {
8536            continue;
8537        };
8538        let arguments = crate::schema::computed_caller_arguments(computed, schema_cache);
8539        let given = match arguments.is_empty() {
8540            true => None,
8541            false => embed_arguments(field).remove("args"),
8542        };
8543        projections.push(format!(
8544            "{} AS {}",
8545            computed_column_call(
8546                computed,
8547                row_reference,
8548                name,
8549                &arguments,
8550                given.as_ref(),
8551                param_idx,
8552                values,
8553            )?,
8554            postrust_sql::escape_ident(name)
8555        ));
8556    }
8557    Ok(projections)
8558}
8559
8560/// The call behind a computed field, including whatever the caller passed.
8561///
8562/// The ordinary case is a function of the row alone, which is written
8563/// positionally: that is the call REST writes, and the one a client that never
8564/// heard of `args` is asking for. Anything else has to be written by name --
8565/// the row is then not the only parameter and may not be the first -- so the
8566/// row, the session where the function asks for one, and each argument the
8567/// caller gave are all named.
8568fn computed_column_call(
8569    computed: &postrust_core::schema_cache::ComputedColumn,
8570    row: &str,
8571    field_name: &str,
8572    arguments: &[(String, String, bool)],
8573    given: Option<&serde_json::Value>,
8574    param_idx: &mut usize,
8575    values: &mut Vec<serde_json::Value>,
8576) -> Result<String, async_graphql::Error> {
8577    if arguments.is_empty() {
8578        return Ok(computed_call(computed, row, row));
8579    }
8580    let Some(row_argument) = &computed.row_argument else {
8581        return Ok(computed_call(computed, row, row));
8582    };
8583    let function = format!(
8584        "{}.{}",
8585        postrust_sql::escape_ident(&computed.function.schema),
8586        postrust_sql::escape_ident(&computed.function.name)
8587    );
8588    let mut passed = vec![format!(
8589        "{} => {}",
8590        postrust_sql::escape_ident(row_argument),
8591        row
8592    )];
8593    if let Some(session) = &computed.session_argument {
8594        passed.push(format!(
8595            "{} => {}",
8596            postrust_sql::escape_ident(session),
8597            SESSION_ARGUMENT
8598        ));
8599    }
8600    for (name, pg_type, required) in arguments {
8601        match given.and_then(|given| given.get(name)) {
8602            None if *required => {
8603                return Err(async_graphql::Error::new(format!(
8604                    "{} needs the argument \"{}\"",
8605                    field_name, name
8606                )))
8607            }
8608            // Left out is left out, so the function's own default applies.
8609            None => continue,
8610            Some(value) => {
8611                passed.push(format!(
8612                    "{} => ${}::{}",
8613                    postrust_sql::escape_ident(name),
8614                    param_idx,
8615                    pg_type
8616                ));
8617                *param_idx += 1;
8618                values.push(value.clone());
8619            }
8620        }
8621    }
8622    Ok(format!("{}({})", function, passed.join(", ")))
8623}
8624
8625/// The `count(...)` a selection asked for.
8626///
8627/// `count` alone is `count(*)`, which counts rows. `count(columns: [a])` is
8628/// `count("a")`, which counts the rows where `a` is not null -- a different
8629/// number, and the one the client asked for. `distinct: true` counts the
8630/// distinct values among them, and several columns are counted as the row
8631/// they make, which is how PostgreSQL counts more than one thing at once.
8632///
8633/// The column names come from the generated enum, so a selection can only
8634/// name a column the table has; what it may not know is the column's own name
8635/// where the schema exposes it under another.
8636fn count_expression(
8637    field: async_graphql::SelectionField<'_>,
8638    table: (&str, &str),
8639    alias: Option<&str>,
8640    names: &crate::names::NameOverrides,
8641) -> String {
8642    let arguments = embed_arguments(field);
8643    let requested = match arguments.get("columns") {
8644        Some(serde_json::Value::Array(items)) => items
8645            .iter()
8646            .filter_map(|item| item.as_str())
8647            .collect::<Vec<_>>(),
8648        Some(serde_json::Value::String(one)) => vec![one.as_str()],
8649        _ => Vec::new(),
8650    };
8651    if requested.is_empty() {
8652        return "count(*)".to_string();
8653    }
8654    let columns: Vec<String> = requested
8655        .into_iter()
8656        .map(|column| {
8657            let (schema, name) = table;
8658            let source = column_for(names, schema, name, column);
8659            match alias {
8660                Some(alias) => format!(
8661                    "{}.{}",
8662                    postrust_sql::escape_ident(alias),
8663                    postrust_sql::escape_ident(source)
8664                ),
8665                None => postrust_sql::escape_ident(source),
8666            }
8667        })
8668        .collect();
8669    // One column counts itself; several count the row they make, since
8670    // `count(a, b)` is not a call PostgreSQL has.
8671    let counted = match columns.len() {
8672        1 => columns.into_iter().next().unwrap_or_default(),
8673        _ => format!("({})", columns.join(", ")),
8674    };
8675    let distinct = matches!(
8676        arguments.get("distinct"),
8677        Some(serde_json::Value::Bool(true))
8678    );
8679    match distinct {
8680        true => format!("count(DISTINCT {})", counted),
8681        false => format!("count({})", counted),
8682    }
8683}
8684
8685/// The SELECT list for a nested aggregate.
8686///
8687/// `articles_aggregate { aggregate { count } nodes { title } }` becomes one
8688/// row per parent, correlated the way any embed is. Both halves are aggregates
8689/// over the same correlated set, so they go in one select list rather than two
8690/// queries -- `count(*)` and `json_agg(...)` read the same rows.
8691#[allow(clippy::too_many_arguments)] // the recursion carries its whole context
8692fn nested_aggregate_select(
8693    caller: &crate::role::Caller<'_>,
8694    selection: async_graphql::SelectionField<'_>,
8695    child_alias: &str,
8696    schema_cache: &SchemaCache,
8697    relationships: &HashMap<String, Vec<RelationshipField>>,
8698    target_type: &str,
8699    child_table: Option<&postrust_core::schema_cache::Table>,
8700    max_rows: Option<i64>,
8701    alias_counter: &mut usize,
8702    param_idx: &mut usize,
8703    values: &mut Vec<serde_json::Value>,
8704    names: &crate::names::NameOverrides,
8705) -> Result<String, async_graphql::Error> {
8706    use crate::schema::aggregate as agg;
8707
8708    let mut parts: Vec<String> = Vec::new();
8709
8710    for child in selection.selection_set() {
8711        match child.name() {
8712            "aggregate" => {
8713                let of_table = child_table
8714                    .map(|table| (table.schema.as_str(), table.name.as_str()))
8715                    .unwrap_or(("", ""));
8716                // Each `count` is answered under the name it was asked for,
8717                // since two of them may count different things. Every other
8718                // aggregate is one object read under however many names, so
8719                // its columns are the union of what all of them named.
8720                let mut counts: Vec<(String, String)> = Vec::new();
8721                let mut wanted: BTreeMap<&str, Vec<String>> = BTreeMap::new();
8722                for function in child.selection_set() {
8723                    if function.name() == "count" {
8724                        counts.push((
8725                            function.alias().unwrap_or("count").to_string(),
8726                            count_expression(function, of_table, Some(child_alias), names),
8727                        ));
8728                        continue;
8729                    }
8730                    let sql_function = agg::NUMERIC_AGGREGATES
8731                        .iter()
8732                        .chain(agg::ORDERED_AGGREGATES.iter())
8733                        .find(|(name, _)| *name == function.name())
8734                        .map(|(name, _)| *name);
8735                    let Some(sql_function) = sql_function else {
8736                        continue;
8737                    };
8738                    let columns = wanted.entry(sql_function).or_default();
8739                    for column in function.selection_set() {
8740                        let name = column.name().to_string();
8741                        // Answered by the schema, not a column.
8742                        if name != "__typename" && !columns.contains(&name) {
8743                            columns.push(name);
8744                        }
8745                    }
8746                }
8747                // Answered whether or not it was asked for, for the reason
8748                // the root aggregate answers it.
8749                let mut fields = match counts.iter().any(|(key, _)| key == "count") {
8750                    true => Vec::new(),
8751                    false => vec!["'count', count(*)".to_string()],
8752                };
8753                for (key, expression) in &counts {
8754                    fields.push(format!(
8755                        "{}, {}",
8756                        postrust_sql::quote_literal(key),
8757                        expression
8758                    ));
8759                }
8760                for (sql_function, named) in &wanted {
8761                    let columns: Vec<String> = named
8762                        .iter()
8763                        .map(|column| {
8764                            let source = child_table
8765                                .map(|table| table_column_for(names, table, column))
8766                                .unwrap_or(column.as_str());
8767                            format!(
8768                                "{}, {}({}.{})",
8769                                postrust_sql::quote_literal(column),
8770                                sql_function,
8771                                postrust_sql::escape_ident(child_alias),
8772                                postrust_sql::escape_ident(source)
8773                            )
8774                        })
8775                        .collect();
8776                    // Pushed even when empty, as at the root: `max { __typename }`
8777                    // is an object with nothing in it, not a null.
8778                    fields.push(format!(
8779                        "'{}', json_build_object({})",
8780                        sql_function,
8781                        columns.join(", ")
8782                    ));
8783                }
8784                parts.push(format!(
8785                    "json_build_object({}) AS {}",
8786                    fields.join(", "),
8787                    postrust_sql::escape_ident("aggregate")
8788                ));
8789            }
8790            // `nodes` under an aggregate is a rows selection like any other:
8791            // what it names may be a column, a computed field, or a
8792            // relationship of its own, and the last of those is a further
8793            // correlated subselect rather than a column of this one.
8794            "nodes" => {
8795                let embeds = build_embed_expressions(
8796                    caller,
8797                    schema_cache,
8798                    relationships,
8799                    target_type,
8800                    child_alias,
8801                    child.selection_set(),
8802                    max_rows,
8803                    alias_counter,
8804                    param_idx,
8805                    values,
8806                    names,
8807                )?;
8808                let mut columns: Vec<String> = Vec::new();
8809                for column in child.selection_set() {
8810                    let name = column.name();
8811                    if name == "__typename" || embeds.iter().any(|(field, _)| field == name) {
8812                        continue;
8813                    }
8814                    columns.push(format!(
8815                        "{}, {}",
8816                        postrust_sql::quote_literal(name),
8817                        column_expression(child_table, child_alias, name, names)
8818                    ));
8819                }
8820                for (field, expression) in &embeds {
8821                    columns.push(format!(
8822                        "{}, {}",
8823                        postrust_sql::quote_literal(field),
8824                        expression
8825                    ));
8826                }
8827                let row = if columns.is_empty() {
8828                    format!("row_to_json({})", postrust_sql::escape_ident(child_alias))
8829                } else {
8830                    format!("json_build_object({})", columns.join(", "))
8831                };
8832                parts.push(format!(
8833                    "COALESCE(json_agg({}), '[]'::json) AS {}",
8834                    row,
8835                    postrust_sql::escape_ident("nodes")
8836                ));
8837            }
8838            _ => {}
8839        }
8840    }
8841
8842    if parts.is_empty() {
8843        // A selection of nothing but `__typename`. One row, no columns, is not
8844        // valid SQL; a count nobody reads is.
8845        parts.push("count(*) AS pgrst_empty".to_string());
8846    }
8847    Ok(parts.join(", "))
8848}
8849
8850/// How one named field of a row is read, given the alias the row goes by.
8851///
8852/// A column is itself; a computed field is the call that produces it. Which of
8853/// the two a name is depends on the table, so this is the one place that asks.
8854fn column_expression(
8855    table: Option<&postrust_core::schema_cache::Table>,
8856    alias: &str,
8857    name: &str,
8858    names: &crate::names::NameOverrides,
8859) -> String {
8860    let column = table
8861        .map(|t| table_column_for(names, t, name))
8862        .unwrap_or(name);
8863    let plain = format!(
8864        "{}.{}",
8865        postrust_sql::escape_ident(alias),
8866        postrust_sql::escape_ident(column)
8867    );
8868    let computed = table
8869        .filter(|t| t.get_column(column).is_none())
8870        .and_then(|t| {
8871            let function = names
8872                .computed_source(&t.schema, &t.name, name)
8873                .unwrap_or(name);
8874            t.get_computed_column(function)
8875        });
8876    match computed {
8877        Some(definition) => computed_call(
8878            definition,
8879            &format!("{}.*", postrust_sql::escape_ident(alias)),
8880            &postrust_sql::escape_ident(alias),
8881        ),
8882        None => plain,
8883    }
8884}
8885
8886/// Render `order_by` terms against a table, qualified with an alias.
8887///
8888/// The root field's ordering reads its argument from the resolver context and
8889/// needs the schema cache asynchronously; an embed already holds both, so this
8890/// takes the value directly. Columns are checked against the table before they
8891/// are quoted, so an unknown or crafted name is refused rather than
8892/// interpolated.
8893fn order_terms(
8894    order: &serde_json::Value,
8895    schema_cache: &SchemaCache,
8896    schema_name: &str,
8897    table_name: &str,
8898    alias: &str,
8899    names: &crate::names::NameOverrides,
8900) -> Result<Option<String>, async_graphql::Error> {
8901    let entries: Vec<&serde_json::Value> = match order {
8902        serde_json::Value::Array(items) => items.iter().collect(),
8903        serde_json::Value::Object(_) => vec![order],
8904        _ => return Ok(None),
8905    };
8906
8907    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8908    let table = schema_cache
8909        .get_table(&qi)
8910        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8911
8912    let mut terms = Vec::new();
8913    for entry in entries {
8914        let serde_json::Value::Object(map) = entry else {
8915            continue;
8916        };
8917        for (field, direction) in map {
8918            // A direction given as null is no direction, as at the root.
8919            if direction.is_null() {
8920                continue;
8921            }
8922            let column = table_column_for(names, table, field);
8923            if table.get_column(column).is_none() {
8924                return Err(async_graphql::Error::new(format!(
8925                    "cannot order by unknown column \"{}\" on \"{}\"",
8926                    field, table_name
8927                )));
8928            }
8929            let name = direction.as_str().unwrap_or_default();
8930            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8931                async_graphql::Error::new(format!("\"{}\" is not a sort direction", name))
8932            })?;
8933            terms.push(format!(
8934                "{}.{} {}",
8935                postrust_sql::escape_ident(alias),
8936                postrust_sql::escape_ident(column),
8937                sql
8938            ));
8939        }
8940    }
8941
8942    Ok(if terms.is_empty() {
8943        None
8944    } else {
8945        Some(terms.join(", "))
8946    })
8947}
8948
8949/// Whether a to-one relationship always has a row at the other end.
8950///
8951/// It does when this row's own key columns cannot be null: a foreign key
8952/// guarantees a matching row for every value, so a `NOT NULL` key is a
8953/// relationship that cannot be absent, and the field is non-null. A client
8954/// generating types then gets `author` rather than `author | null`, which is
8955/// what the schema actually promises.
8956///
8957/// Only where a foreign key says so. A computed relationship is a function
8958/// call, and a one-to-one reached through the *child's* key is a row that may
8959/// simply not exist.
8960fn always_present(rel: &RelationshipField, table: &postrust_core::schema_cache::Table) -> bool {
8961    use postrust_core::schema_cache::{Cardinality, Relationship};
8962    let Relationship::ForeignKey { cardinality, .. } = &rel.relationship else {
8963        return false;
8964    };
8965    match cardinality {
8966        Cardinality::M2O { .. } => {}
8967        Cardinality::O2O {
8968            is_parent: false, ..
8969        } => {}
8970        _ => return false,
8971    }
8972    let columns = cardinality.columns();
8973    !columns.is_empty()
8974        && columns.iter().all(|(local, _)| {
8975            table
8976                .get_column(local)
8977                .is_some_and(|column| !column.nullable)
8978        })
8979}
8980
8981/// Whether the row at the other end of a relationship carries the key.
8982///
8983/// If it does, this row is written first and its key is pushed down; if this
8984/// row's own column carries it, the other row is written first and its key is
8985/// read back. Cardinality alone does not answer it: a one-to-one is one row in
8986/// either direction, and which side holds the foreign key is the whole
8987/// difference between the two orders.
8988fn child_holds_the_key(relationship: &postrust_core::schema_cache::Relationship) -> bool {
8989    use postrust_core::schema_cache::{Cardinality, Relationship};
8990    match relationship {
8991        Relationship::ForeignKey { cardinality, .. } => match cardinality {
8992            Cardinality::M2O { .. } => false,
8993            Cardinality::O2O { is_parent, .. } => *is_parent,
8994            _ => true,
8995        },
8996        Relationship::Computed { .. } => true,
8997    }
8998}
8999
9000/// The name of a function's session parameter, if it has one.
9001fn computed_session_argument(
9002    cache: &SchemaCache,
9003    function: &postrust_core::api_request::QualifiedIdentifier,
9004    row_argument: &str,
9005) -> Option<String> {
9006    let _ = row_argument;
9007    cache
9008        .routines
9009        .get(function)
9010        .into_iter()
9011        .flatten()
9012        .find(|routine| routine.name == function.name)?
9013        .params
9014        .iter()
9015        .find(|param| {
9016            param.name == "hasura_session" && matches!(param.param_type.as_str(), "json" | "jsonb")
9017        })
9018        .map(|param| param.name.clone())
9019}
9020
9021/// The name of the input holding a computed relationship's own arguments.
9022fn computed_args_type_name(type_name: &str, field: &str) -> String {
9023    format!("{}_{}_args", type_name, field)
9024}
9025
9026/// The argument list a computed relationship's function is called with.
9027///
9028/// `None` where the parent row is the only thing it takes -- the ordinary case,
9029/// and the positional call the REST surface writes. Anything else is written by
9030/// name: the row, the session where the function asks for one, and whatever the
9031/// client put under `args`.
9032fn computed_arguments(
9033    relationship: &RelationshipField,
9034    plan: &postrust_core::embed::EmbedPlan,
9035    parent_row: &str,
9036    given: Option<&serde_json::Value>,
9037    param_idx: &mut usize,
9038    values: &mut Vec<serde_json::Value>,
9039    schema_cache: &SchemaCache,
9040) -> Result<Option<String>, async_graphql::Error> {
9041    let (Some(function), Some(row_argument)) = (&plan.function, &plan.row_argument) else {
9042        return Ok(None);
9043    };
9044    let session = schema_cache
9045        .routines
9046        .get(function)
9047        .into_iter()
9048        .flatten()
9049        .find(|routine| routine.name == function.name)
9050        .and_then(|routine| {
9051            routine
9052                .params
9053                .iter()
9054                .find(|param| {
9055                    param.name == "hasura_session"
9056                        && matches!(param.param_type.as_str(), "json" | "jsonb")
9057                })
9058                .map(|param| param.name.clone())
9059        });
9060    if relationship.arguments.is_empty() && session.is_none() {
9061        return Ok(None);
9062    }
9063
9064    let mut passed = vec![format!(
9065        "{} => {}",
9066        postrust_sql::escape_ident(row_argument),
9067        parent_row
9068    )];
9069    if let Some(session) = session {
9070        passed.push(format!(
9071            "{} => {}",
9072            postrust_sql::escape_ident(&session),
9073            SESSION_ARGUMENT
9074        ));
9075    }
9076    for (name, pg_type, required) in &relationship.arguments {
9077        match given.and_then(|given| given.get(name)) {
9078            None if *required => {
9079                return Err(async_graphql::Error::new(format!(
9080                    "{} needs the argument \"{}\"",
9081                    relationship.name, name
9082                )))
9083            }
9084            // Left out is left out, so the function's own default applies.
9085            None => continue,
9086            Some(value) => {
9087                passed.push(format!(
9088                    "{} => ${}::{}",
9089                    postrust_sql::escape_ident(name),
9090                    param_idx,
9091                    pg_type
9092                ));
9093                *param_idx += 1;
9094                values.push(value.clone());
9095            }
9096        }
9097    }
9098    Ok(Some(passed.join(", ")))
9099}
9100
9101/// The arguments written on an embedded field, as JSON.
9102fn embed_arguments(field: async_graphql::SelectionField<'_>) -> HashMap<String, serde_json::Value> {
9103    field
9104        .arguments()
9105        .map(|args| {
9106            args.into_iter()
9107                .map(|(name, value)| {
9108                    (
9109                        name.to_string(),
9110                        plain_numbers(value.into_json().unwrap_or(serde_json::Value::Null)),
9111                    )
9112                })
9113                .collect()
9114        })
9115        .unwrap_or_default()
9116}
9117
9118/// What an embed's arguments narrow its child rows to: a `DISTINCT ON`
9119/// prefix, a predicate, an ordering, a limit and an offset, in the order the
9120/// SQL takes them.
9121type Narrowing = (String, Option<String>, Option<String>, Option<i64>, i64);
9122
9123/// What an embed's arguments narrow its child rows to.
9124///
9125/// A list and an aggregate of that same list take the same four arguments and
9126/// mean the same thing by them; only what is done with the surviving rows
9127/// differs, so the reading of the arguments is shared.
9128#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
9129fn embed_narrowing(
9130    arguments: &HashMap<String, serde_json::Value>,
9131    plan: &postrust_core::embed::EmbedPlan,
9132    schema_cache: &SchemaCache,
9133    relationships: &HashMap<String, Vec<RelationshipField>>,
9134    target_type: &str,
9135    child_alias: &str,
9136    max_rows: Option<i64>,
9137    param_idx: &mut usize,
9138    values: &mut Vec<serde_json::Value>,
9139    names: &crate::names::NameOverrides,
9140    caller: &crate::role::Caller<'_>,
9141) -> Result<Narrowing, async_graphql::Error> {
9142    // The child's own permission, which is the whole reason an embed is a
9143    // place this has to be applied: `author { articles { ... } }` reads two
9144    // tables, and the rule on the second is not the rule on the first.
9145    let permission =
9146        permission_predicate(caller, names, &plan.foreign_schema, &plan.foreign_table)?;
9147    let requested = arguments.get("where").filter(|v| !v.is_null()).cloned();
9148    let child_where = match and_predicates(requested, permission) {
9149        Some(expression) => {
9150            let child_scope = WhereScope::for_alias(
9151                &plan.foreign_schema,
9152                &plan.foreign_table,
9153                child_alias,
9154                target_type,
9155                schema_cache,
9156                relationships,
9157                names,
9158            )
9159            .for_caller(*caller);
9160            let mut nested_alias = 0usize;
9161            build_condition(
9162                &expression,
9163                &child_scope,
9164                param_idx,
9165                values,
9166                &mut nested_alias,
9167            )?
9168        }
9169        None => None,
9170    };
9171
9172    let child_order = match arguments.get("order_by") {
9173        Some(order) if !order.is_null() => order_terms(
9174            order,
9175            schema_cache,
9176            &plan.foreign_schema,
9177            &plan.foreign_table,
9178            child_alias,
9179            names,
9180        )?,
9181        _ => None,
9182    };
9183
9184    // A limit written on the embed is what the client asked for; the
9185    // configured ceiling still applies as an upper bound, the same way it
9186    // does at the top level -- and so does the child's own permission, which
9187    // bounds rows per parent here rather than per query.
9188    let ceiling = match (
9189        max_rows,
9190        crate::role::read_limit(caller, names, &plan.foreign_schema, &plan.foreign_table),
9191    ) {
9192        (Some(configured), Some(granted)) => Some(configured.min(granted)),
9193        (only, None) | (None, only) => only,
9194    };
9195    let child_limit = match arguments.get("limit").and_then(|v| v.as_i64()) {
9196        Some(requested) => match ceiling {
9197            Some(ceiling) => Some(requested.min(ceiling)),
9198            None => Some(requested),
9199        },
9200        None => ceiling,
9201    };
9202    let child_offset = arguments
9203        .get("offset")
9204        .and_then(|v| v.as_i64())
9205        .unwrap_or(0);
9206
9207    // `author { articles(distinct_on: [title], order_by: {title: asc}) }` --
9208    // one article per title, the same question the root field answers and
9209    // under the same rule about what may be ordered by.
9210    let requested = match arguments.get("distinct_on") {
9211        Some(serde_json::Value::Array(items)) => items
9212            .iter()
9213            .filter_map(|item| item.as_str())
9214            .collect::<Vec<_>>(),
9215        Some(serde_json::Value::String(one)) => vec![one.as_str()],
9216        _ => Vec::new(),
9217    };
9218    let mut distinct_on = Vec::with_capacity(requested.len());
9219    if !requested.is_empty() {
9220        let qi = postrust_core::api_request::QualifiedIdentifier::new(
9221            &plan.foreign_schema,
9222            &plan.foreign_table,
9223        );
9224        let table = schema_cache.get_table(&qi).ok_or_else(|| {
9225            async_graphql::Error::new(format!("unknown table \"{}\"", plan.foreign_table))
9226        })?;
9227        for field in requested {
9228            let column = table_column_for(names, table, field).to_string();
9229            if table.get_column(&column).is_none() {
9230                return Err(async_graphql::Error::new(format!(
9231                    "cannot take distinct on unknown column \"{}\" of \"{}\"",
9232                    field, plan.foreign_table
9233                )));
9234            }
9235            distinct_on.push(format!(
9236                "{}.{}",
9237                postrust_sql::escape_ident(child_alias),
9238                postrust_sql::escape_ident(&column)
9239            ));
9240        }
9241    }
9242    let (distinct, child_order) = distinct_on_clause(&distinct_on, child_order.as_deref())?;
9243
9244    Ok((
9245        distinct,
9246        child_where,
9247        child_order,
9248        child_limit,
9249        child_offset,
9250    ))
9251}
9252
9253#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
9254fn build_embed_expressions<'a>(
9255    caller: &crate::role::Caller<'_>,
9256    schema_cache: &SchemaCache,
9257    relationships: &HashMap<String, Vec<RelationshipField>>,
9258    type_name: &str,
9259    parent_alias: &str,
9260    fields: impl IntoIterator<Item = SelectionField<'a>>,
9261    max_rows: Option<i64>,
9262    alias_counter: &mut usize,
9263    param_idx: &mut usize,
9264    values: &mut Vec<serde_json::Value>,
9265    names: &crate::names::NameOverrides,
9266) -> Result<Vec<(String, String)>, async_graphql::Error> {
9267    let Some(available) = relationships.get(type_name) else {
9268        return Ok(Vec::new());
9269    };
9270
9271    let mut expressions = Vec::new();
9272
9273    for field in fields {
9274        // `<relationship>_aggregate` is the same embed with an aggregate
9275        // select list, so it is resolved here rather than by a resolver of its
9276        // own -- the correlation is what makes it a per-parent answer.
9277        let aggregate_of = field
9278            .name()
9279            .strip_suffix("_aggregate")
9280            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list));
9281
9282        if let Some(rel) = aggregate_of {
9283            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9284                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9285            *alias_counter += 1;
9286            let child_alias = format!("a{}", alias_counter);
9287
9288            // The same narrowing an embedded list takes. `where` here decides
9289            // which rows are counted, not which are returned, so it goes on the
9290            // set the aggregate reads rather than on the answer.
9291            let arguments = embed_arguments(field);
9292            let (child_distinct, child_where, child_order, child_limit, child_offset) =
9293                embed_narrowing(
9294                    &arguments,
9295                    &plan,
9296                    schema_cache,
9297                    relationships,
9298                    &rel.target_type,
9299                    &child_alias,
9300                    max_rows,
9301                    param_idx,
9302                    values,
9303                    names,
9304                    caller,
9305                )?;
9306
9307            let child_table =
9308                schema_cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
9309                    &plan.foreign_schema,
9310                    &plan.foreign_table,
9311                ));
9312            let select = nested_aggregate_select(
9313                caller,
9314                field,
9315                &child_alias,
9316                schema_cache,
9317                relationships,
9318                &rel.target_type,
9319                child_table,
9320                max_rows,
9321                alias_counter,
9322                param_idx,
9323                values,
9324                names,
9325            )?;
9326
9327            let call = computed_arguments(
9328                rel,
9329                &plan,
9330                &postrust_sql::escape_ident(parent_alias),
9331                arguments.get("args"),
9332                param_idx,
9333                values,
9334                schema_cache,
9335            )?;
9336            let expression = plan
9337                .aggregate_expression(
9338                    parent_alias,
9339                    &postrust_sql::escape_ident(parent_alias),
9340                    &child_alias,
9341                    &select,
9342                    child_limit,
9343                    child_offset,
9344                    child_where.as_deref(),
9345                    child_order.as_deref(),
9346                    call.as_deref(),
9347                    &child_distinct,
9348                    // A GraphQL body is JSON throughout -- nothing here
9349                    // renders a row as text -- and Hasura keeps the order the
9350                    // fields were selected in.
9351                    postrust_core::EmbedRendering::Verbatim,
9352                )
9353                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9354            expressions.push((field.name().to_string(), expression));
9355            continue;
9356        }
9357
9358        let Some(rel) = available.iter().find(|r| r.name == field.name()) else {
9359            continue;
9360        };
9361
9362        let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9363            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9364
9365        *alias_counter += 1;
9366        let child_alias = format!("e{}", alias_counter);
9367
9368        let nested = build_embed_expressions(
9369            caller,
9370            schema_cache,
9371            relationships,
9372            &rel.target_type,
9373            &child_alias,
9374            field.selection_set(),
9375            max_rows,
9376            alias_counter,
9377            param_idx,
9378            values,
9379            names,
9380        )?;
9381
9382        // The arguments written on the embed itself.
9383        let arguments = embed_arguments(field);
9384        let (child_distinct, child_where, child_order, child_limit, child_offset) =
9385            embed_narrowing(
9386                &arguments,
9387                &plan,
9388                schema_cache,
9389                relationships,
9390                &rel.target_type,
9391                &child_alias,
9392                max_rows,
9393                param_idx,
9394                values,
9395                names,
9396                caller,
9397            )?;
9398
9399        // Leaf fields are columns; anything that resolved to a relationship is
9400        // an expression instead.
9401        let child_relationships = relationships.get(&rel.target_type);
9402        let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
9403            &plan.foreign_schema,
9404            &plan.foreign_table,
9405        );
9406        let child_table = schema_cache.get_table(&child_qi);
9407        let mut parts: Vec<String> = Vec::new();
9408        for sub in field.selection_set() {
9409            let name = sub.name();
9410            let is_relationship = child_relationships
9411                .map(|rels| {
9412                    rels.iter().any(|r| {
9413                        r.name == name || (r.is_list && format!("{}_aggregate", r.name) == name)
9414                    })
9415                })
9416                .unwrap_or(false);
9417            // `__typename` is answered by the schema, not the row -- and it is
9418            // asked for here constantly: Apollo Client adds it to every
9419            // selection, and a Federation router to every entity reference.
9420            if is_relationship || name == "__typename" {
9421                continue;
9422            }
9423            // A computed column inside an embed is the same call, against the
9424            // child's alias rather than the table.
9425            let column = child_table
9426                .map(|t| table_column_for(names, t, name))
9427                .unwrap_or(name);
9428            let computed = child_table
9429                .filter(|t| t.get_column(column).is_none())
9430                .and_then(|t| {
9431                    let function = names
9432                        .computed_source(&t.schema, &t.name, name)
9433                        .unwrap_or(name);
9434                    t.get_computed_column(function)
9435                });
9436            match computed {
9437                Some(definition) => parts.push(format!(
9438                    "{} AS {}",
9439                    computed_call(
9440                        definition,
9441                        &format!("{}.*", postrust_sql::escape_ident(&child_alias)),
9442                        &postrust_sql::escape_ident(&child_alias),
9443                    ),
9444                    postrust_sql::escape_ident(name)
9445                )),
9446                // A column the child exposes under another name is selected as
9447                // that name, since the row it lands in is the client's.
9448                None if column != name => parts.push(format!(
9449                    "{} AS {}",
9450                    postrust_sql::escape_ident(column),
9451                    postrust_sql::escape_ident(name)
9452                )),
9453                None => parts.push(postrust_sql::escape_ident(name)),
9454            }
9455        }
9456        if parts.is_empty() && nested.is_empty() {
9457            parts.push(
9458                child_table
9459                    .and_then(|table| rename_projection(table, &child_alias, names))
9460                    .unwrap_or_else(|| format!("{}.*", postrust_sql::escape_ident(&child_alias))),
9461            );
9462        }
9463        for (field_name, expression) in nested {
9464            parts.push(format!(
9465                "{} AS {}",
9466                expression,
9467                postrust_sql::escape_ident(&field_name)
9468            ));
9469        }
9470
9471        let computed_call_arguments = computed_arguments(
9472            rel,
9473            &plan,
9474            &postrust_sql::escape_ident(parent_alias),
9475            arguments.get("args"),
9476            param_idx,
9477            values,
9478            schema_cache,
9479        )?;
9480        let expression = plan
9481            .embed_expression(
9482                parent_alias,
9483                // A computed relationship is correlated by argument rather
9484                // than by a key: the function takes the parent row, and an
9485                // alias names that row.
9486                &postrust_sql::escape_ident(parent_alias),
9487                &child_alias,
9488                &format!("{}{}", child_distinct, parts.join(", ")),
9489                child_limit,
9490                child_offset,
9491                child_where.as_deref(),
9492                child_order.as_deref(),
9493                computed_call_arguments.as_deref(),
9494                postrust_core::EmbedRendering::Verbatim,
9495            )
9496            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9497
9498        expressions.push((rel.name.clone(), expression));
9499    }
9500
9501    Ok(expressions)
9502}
9503
9504/// What embedding a relationship needs, independent of the rows involved.
9505///
9506/// The connection is passed alongside rather than held here: embedding runs on
9507/// the request's transaction, and a shared reference to this struct could not
9508/// hand out the mutable borrow the queries need.
9509struct EmbedContext<'c> {
9510    schema_cache: &'c SchemaCache,
9511    relationships: &'c HashMap<String, Vec<RelationshipField>>,
9512    max_rows: Option<i64>,
9513}
9514
9515/// Embed the relationship fields requested on `rows`.
9516///
9517/// One query per relationship per level, not one per row: the parents' join
9518/// keys are collected and passed as a single array. Recurses so a nested
9519/// selection costs one further query per relationship at each depth.
9520fn embed_relationships<'f>(
9521    conn: &'f mut sqlx::PgConnection,
9522    ctx: &'f EmbedContext<'f>,
9523    type_name: &'f str,
9524    selection: async_graphql::SelectionField<'f>,
9525    rows: &'f mut [serde_json::Value],
9526) -> futures::future::BoxFuture<'f, Result<(), async_graphql::Error>> {
9527    Box::pin(async move {
9528        if rows.is_empty() {
9529            return Ok(());
9530        }
9531
9532        let Some(available) = ctx.relationships.get(type_name) else {
9533            return Ok(());
9534        };
9535
9536        for requested in selection.selection_set() {
9537            let Some(rel) = available.iter().find(|r| r.name == requested.name()) else {
9538                continue;
9539            };
9540
9541            let plan =
9542                postrust_core::embed::EmbedPlan::resolve(&rel.relationship, ctx.schema_cache)
9543                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9544
9545            let keys = postrust_core::embed::parent_keys(rows, &plan.local_column);
9546
9547            // Project only what the selection asked for. A GraphQL selection
9548            // names its leaf fields, so the columns are known before the query
9549            // and an unrequested column need not be read, serialised, sent and
9550            // parsed just to be dropped.
9551            //
9552            // A nested relationship joins on a column of the child row, so that
9553            // column is added even when it was not selected; it is removed again
9554            // when the response is shaped. A selection whose sub-fields cannot
9555            // all be resolved to columns falls back to every column.
9556            let mut child_columns: Vec<String> = Vec::new();
9557            let mut project_everything = false;
9558            let child_relationships = ctx.relationships.get(&rel.target_type);
9559            for field in requested.selection_set() {
9560                let name = field.name();
9561                match child_relationships.and_then(|rels| rels.iter().find(|r| r.name == name)) {
9562                    Some(nested_rel) => {
9563                        match postrust_core::embed::EmbedPlan::resolve(
9564                            &nested_rel.relationship,
9565                            ctx.schema_cache,
9566                        ) {
9567                            Ok(nested_plan) => child_columns.push(nested_plan.local_column),
9568                            Err(_) => project_everything = true,
9569                        }
9570                    }
9571                    // Answered by the schema, not a column.
9572                    None if name == "__typename" => {}
9573                    None => child_columns.push(name.to_string()),
9574                }
9575            }
9576            if project_everything || child_columns.is_empty() {
9577                child_columns.clear();
9578            }
9579
9580            let mut grouped = if keys.is_empty() {
9581                std::collections::HashMap::new()
9582            } else {
9583                let sql = plan
9584                    .children_grouped_sql(ctx.max_rows, &child_columns)
9585                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9586
9587                let fetched = sqlx::query(AssertSqlSafe(sql))
9588                    .bind(&keys)
9589                    .fetch_all(&mut *conn)
9590                    .await
9591                    .map_err(database_error)?;
9592
9593                // The query returns the join key and a JSON array of that key's
9594                // children, grouped by PostgreSQL rather than row by row here.
9595                use sqlx::Row;
9596                let pairs: Vec<(serde_json::Value, serde_json::Value)> = fetched
9597                    .into_iter()
9598                    .filter_map(|row| {
9599                        Some((
9600                            row.try_get::<serde_json::Value, _>(0).ok()?,
9601                            row.try_get::<serde_json::Value, _>(1).ok()?,
9602                        ))
9603                    })
9604                    .collect();
9605
9606                postrust_core::embed::group_from_aggregated(pairs)
9607            };
9608
9609            // Recurse before attaching, so nested embeds land in the values
9610            // copied onto the parents. One query serves every child row at this
9611            // level, so the rows are flattened for the call and put back
9612            // afterwards; skipped when the selection asks for nothing deeper.
9613            let has_deeper_embed = ctx
9614                .relationships
9615                .get(&rel.target_type)
9616                .map(|rels| {
9617                    requested
9618                        .selection_set()
9619                        .any(|field| rels.iter().any(|r| r.name == field.name()))
9620                })
9621                .unwrap_or(false);
9622
9623            if has_deeper_embed {
9624                let mut order: Vec<(String, usize)> = Vec::with_capacity(grouped.len());
9625                let mut flat: Vec<serde_json::Value> = Vec::new();
9626                for (key, children) in grouped.drain() {
9627                    order.push((key, children.len()));
9628                    flat.extend(children);
9629                }
9630
9631                embed_relationships(&mut *conn, ctx, &rel.target_type, requested, &mut flat)
9632                    .await?;
9633
9634                let mut rest = flat.into_iter();
9635                for (key, count) in order {
9636                    grouped.insert(key, rest.by_ref().take(count).collect());
9637                }
9638            }
9639
9640            for row in rows.iter_mut() {
9641                postrust_core::embed::attach_to_parent(row, &rel.name, &plan, &grouped);
9642            }
9643        }
9644
9645        Ok(())
9646    })
9647}
9648
9649/// Build a `where` document that addresses exactly one row by primary key.
9650///
9651/// Used by the by-PK mutations, which take the key columns as arguments instead
9652/// of a free-form `where`.
9653fn pk_where_from_args(
9654    ctx: &ResolverContext<'_>,
9655    schema_name: &str,
9656    table_name: &str,
9657    pk_columns: &[(String, String)],
9658    names: &crate::names::NameOverrides,
9659) -> Result<serde_json::Value, async_graphql::Error> {
9660    if pk_columns.is_empty() {
9661        return Err(async_graphql::Error::new(format!(
9662            "\"{}\" has no primary key, so it cannot be mutated by key",
9663            table_name
9664        )));
9665    }
9666
9667    // `update_x_by_pk` takes its key as one `pk_columns` object; the others
9668    // take the key columns as arguments of their own. Both spellings are what
9669    // a client was generated against, so both are read here.
9670    let from_object = ctx
9671        .args
9672        .try_get("pk_columns")
9673        .ok()
9674        .map(|v| accessor_to_json(&v));
9675
9676    let mut conditions = serde_json::Map::new();
9677    for (col_name, _) in pk_columns {
9678        // Under the name the key column is exposed as, on both sides: the
9679        // argument the client wrote, and the predicate handed on to the same
9680        // builder every other `where` goes through.
9681        let field = names
9682            .column(schema_name, table_name, col_name)
9683            .unwrap_or(col_name);
9684        let value = match &from_object {
9685            Some(serde_json::Value::Object(map)) => map.get(field).cloned().ok_or_else(|| {
9686                async_graphql::Error::new(format!(
9687                    "pk_columns is missing the key column \"{}\"",
9688                    field
9689                ))
9690            })?,
9691            _ => {
9692                let arg = ctx.args.try_get(field).map_err(|_| {
9693                    async_graphql::Error::new(format!(
9694                        "missing required primary key argument \"{}\"",
9695                        field
9696                    ))
9697                })?;
9698                accessor_to_json(&arg)
9699            }
9700        };
9701        conditions.insert(field.to_string(), serde_json::json!({ "_eq": value }));
9702    }
9703
9704    Ok(serde_json::Value::Object(conditions))
9705}
9706
9707/// GraphQL scalar name to use for a primary key argument of the given
9708/// PostgreSQL type.
9709///
9710/// Falls back to `String` for anything that does not map to a plain scalar --
9711/// a composite or array key cannot be expressed as a single named argument, and
9712/// the value is cast to the column's type in SQL anyway.
9713fn pk_argument_type(pg_type: &str) -> String {
9714    let rendered = crate::types::pg_type_to_graphql(pg_type).to_string();
9715    if rendered.starts_with('[') {
9716        "String".to_string()
9717    } else {
9718        rendered
9719    }
9720}
9721
9722/// The public GraphQL type of a primary-key argument.
9723///
9724/// SQL execution still carries the PostgreSQL type beside the key and uses it
9725/// for casts and binding. This is only the schema-facing type.
9726fn exposed_pk_argument_type(
9727    names: &crate::names::NameOverrides,
9728    schema: &str,
9729    table: &str,
9730    column: &str,
9731    pg_type: &str,
9732) -> String {
9733    names
9734        .column_type(schema, table, column)
9735        .map(|given| given.to_string())
9736        .unwrap_or_else(|| pk_argument_type(pg_type))
9737}
9738
9739/// Convert a GraphQL type string to a TypeRef.
9740fn graphql_type_ref(type_str: &str) -> TypeRef {
9741    // Parse type string like "[Users!]!" or "String" or "Int!"
9742    let is_list = type_str.starts_with('[');
9743    let is_nn = type_str.ends_with('!');
9744
9745    // Strip outer modifiers: first the trailing !, then the brackets
9746    let inner = if is_list {
9747        let stripped = type_str
9748            .trim_end_matches('!') // Remove outer !
9749            .trim_start_matches('[') // Remove [
9750            .trim_end_matches(']'); // Remove ]
9751        stripped
9752    } else {
9753        type_str.trim_end_matches('!')
9754    };
9755
9756    let inner_nn = inner.ends_with('!');
9757    let base_type = inner.trim_end_matches('!');
9758
9759    if is_list {
9760        if is_nn {
9761            if inner_nn {
9762                TypeRef::named_nn_list_nn(base_type)
9763            } else {
9764                TypeRef::named_list_nn(base_type)
9765            }
9766        } else if inner_nn {
9767            TypeRef::named_nn_list(base_type)
9768        } else {
9769            TypeRef::named_list(base_type)
9770        }
9771    } else if is_nn {
9772        TypeRef::named_nn(base_type)
9773    } else {
9774        TypeRef::named(base_type)
9775    }
9776}
9777
9778/// The key serde_json wraps a number in when it is keeping its digits.
9779///
9780/// This crate reads `serde_json` with `arbitrary_precision`, so a PostgreSQL
9781/// `numeric` survives a round trip through JSON with every digit it had. The
9782/// cost is on the way in: a fractional number in a *variable* is deserialized
9783/// into async-graphql's own value type as a one-key object holding the text,
9784/// because the generic path cannot know it is looking at a number. Integers
9785/// are unaffected, which is why this went unnoticed until a shape arrived by
9786/// variable and every coordinate in it read as zero.
9787const NUMBER_AS_TEXT: &str = "$serde_json::private::Number";
9788
9789/// The number an arbitrary-precision wrapper is holding, if that is what this
9790/// object is.
9791fn number_kept_as_text(
9792    map: &serde_json::Map<String, serde_json::Value>,
9793) -> Option<serde_json::Value> {
9794    if map.len() != 1 {
9795        return None;
9796    }
9797    let text = map.get(NUMBER_AS_TEXT)?.as_str()?;
9798    serde_json::from_str::<serde_json::Value>(text)
9799        .ok()
9800        .filter(|value| value.is_number())
9801}
9802
9803/// The same, applied to a whole document that came in as one.
9804fn plain_numbers(value: serde_json::Value) -> serde_json::Value {
9805    match value {
9806        serde_json::Value::Object(map) => match number_kept_as_text(&map) {
9807            Some(number) => number,
9808            None => serde_json::Value::Object(
9809                map.into_iter()
9810                    .map(|(k, v)| (k, plain_numbers(v)))
9811                    .collect(),
9812            ),
9813        },
9814        serde_json::Value::Array(items) => {
9815            serde_json::Value::Array(items.into_iter().map(plain_numbers).collect())
9816        }
9817        other => other,
9818    }
9819}
9820
9821/// Convert ValueAccessor to JSON.
9822fn accessor_to_json(accessor: &ValueAccessor<'_>) -> serde_json::Value {
9823    // Use the deserialize method if available, or convert manually
9824    if accessor.is_null() {
9825        serde_json::Value::Null
9826    } else if let Ok(b) = accessor.boolean() {
9827        serde_json::Value::Bool(b)
9828    } else if let Ok(i) = accessor.i64() {
9829        serde_json::Value::Number(i.into())
9830    } else if let Ok(f) = accessor.f64() {
9831        serde_json::Number::from_f64(f)
9832            .map(serde_json::Value::Number)
9833            .unwrap_or(serde_json::Value::Null)
9834    } else if let Ok(s) = accessor.string() {
9835        serde_json::Value::String(s.to_string())
9836    } else if let Ok(name) = accessor.enum_name() {
9837        // An enum value is not a string to `string()`, and falling through to
9838        // null made every `order_by: {name: asc}` read as an empty direction.
9839        serde_json::Value::String(name.to_string())
9840    } else if let Ok(list) = accessor.list() {
9841        serde_json::Value::Array(list.iter().map(|v| accessor_to_json(&v)).collect())
9842    } else if let Ok(obj) = accessor.object() {
9843        let map: serde_json::Map<String, serde_json::Value> = obj
9844            .iter()
9845            .map(|(k, v)| (k.to_string(), accessor_to_json(&v)))
9846            .collect();
9847        match number_kept_as_text(&map) {
9848            Some(number) => number,
9849            None => serde_json::Value::Object(map),
9850        }
9851    } else {
9852        serde_json::Value::Null
9853    }
9854}
9855
9856/// Convert async-graphql Value to JSON.
9857fn value_to_json(value: &Value) -> serde_json::Value {
9858    match value {
9859        Value::Null => serde_json::Value::Null,
9860        Value::Boolean(b) => serde_json::Value::Bool(*b),
9861        Value::Number(n) => {
9862            if let Some(i) = n.as_i64() {
9863                serde_json::Value::Number(i.into())
9864            } else if let Some(f) = n.as_f64() {
9865                serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
9866            } else {
9867                serde_json::Value::Null
9868            }
9869        }
9870        Value::String(s) => serde_json::Value::String(s.clone()),
9871        Value::List(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
9872        Value::Object(obj) => {
9873            let map: serde_json::Map<String, serde_json::Value> = obj
9874                .iter()
9875                .map(|(k, v)| (k.to_string(), value_to_json(v)))
9876                .collect();
9877            serde_json::Value::Object(map)
9878        }
9879        Value::Binary(b) => serde_json::Value::String(base64::Engine::encode(
9880            &base64::engine::general_purpose::STANDARD,
9881            b,
9882        )),
9883        Value::Enum(e) => serde_json::Value::String(e.to_string()),
9884    }
9885}
9886
9887/// Convert JSON to async-graphql Value.
9888fn json_to_value(json: serde_json::Value) -> Value {
9889    match json {
9890        serde_json::Value::Null => Value::Null,
9891        serde_json::Value::Bool(b) => Value::Boolean(b),
9892        serde_json::Value::Number(n) => {
9893            if let Some(i) = n.as_i64() {
9894                Value::Number(i.into())
9895            } else if let Some(f) = n.as_f64() {
9896                Value::Number(async_graphql::Number::from_f64(f).unwrap())
9897            } else {
9898                Value::Null
9899            }
9900        }
9901        serde_json::Value::String(s) => Value::String(s),
9902        serde_json::Value::Array(arr) => Value::List(arr.into_iter().map(json_to_value).collect()),
9903        serde_json::Value::Object(obj) => {
9904            let map: indexmap::IndexMap<async_graphql::Name, Value> = obj
9905                .into_iter()
9906                .map(|(k, v)| (async_graphql::Name::new(k), json_to_value(v)))
9907                .collect();
9908            Value::Object(map)
9909        }
9910    }
9911}
9912
9913/// Register filter input types.
9914///
9915/// These are currently unreachable: the `filter` and `where` arguments are
9916/// declared as the `JSON` scalar, so no field references these input objects
9917/// and async-graphql prunes them from the published schema (introspecting
9918/// `IntFilterInput` returns null). They are kept as the shape to move to if
9919/// filters become typed per column; until then the operators a filter actually
9920/// supports are the ones `build_where_clause` implements, and it rejects
9921/// anything else rather than ignoring it.
9922#[cfg(test)]
9923mod tests {
9924    use super::*;
9925    use indexmap::IndexMap;
9926    use postrust_core::schema_cache::{Cardinality, Column, Relationship, Table};
9927    use postrust_core::QualifiedIdentifier;
9928    use std::collections::{HashMap, HashSet};
9929
9930    fn entity_key_column(pg_type: &str, explicit_id: bool) -> EntityKeyColumn {
9931        EntityKeyColumn {
9932            field_name: "id".to_string(),
9933            column_name: "id".to_string(),
9934            pg_type: pg_type.to_string(),
9935            explicit_id,
9936        }
9937    }
9938
9939    #[test]
9940    fn federation_integer_keys_reject_strings() {
9941        let column = entity_key_column("int4", false);
9942
9943        assert_eq!(
9944            normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap(),
9945            serde_json::json!(2)
9946        );
9947        let error = normalize_entity_key_value("users", &column, &serde_json::json!("2"))
9948            .expect_err("a GraphQL Int cannot be a string");
9949        assert_eq!(
9950            error.message,
9951            "entity representation for \"users\" has invalid key field \"id\": expected a value compatible with PostgreSQL type \"int4\", found a string"
9952        );
9953    }
9954
9955    #[test]
9956    fn federation_bigint_keys_accept_exact_decimal_strings() {
9957        let column = entity_key_column("int8", false);
9958
9959        assert_eq!(
9960            normalize_entity_key_value("users", &column, &serde_json::json!(7)).unwrap(),
9961            serde_json::json!(7)
9962        );
9963        assert_eq!(
9964            normalize_entity_key_value("users", &column, &serde_json::json!("9007199254740993"))
9965                .unwrap(),
9966            serde_json::json!(9_007_199_254_740_993_i64)
9967        );
9968        assert!(normalize_entity_key_value(
9969            "users",
9970            &column,
9971            &serde_json::json!("9223372036854775808")
9972        )
9973        .is_err());
9974        assert!(normalize_entity_key_value("users", &column, &serde_json::json!("7.5")).is_err());
9975    }
9976
9977    #[test]
9978    fn federation_numeric_keys_accept_exact_decimal_strings() {
9979        let column = entity_key_column("numeric", false);
9980
9981        assert_eq!(
9982            normalize_entity_key_value("users", &column, &serde_json::json!(7.5)).unwrap(),
9983            serde_json::json!("7.5")
9984        );
9985        assert_eq!(
9986            normalize_entity_key_value(
9987                "users",
9988                &column,
9989                &serde_json::json!("9007199254740993.123456789")
9990            )
9991            .unwrap(),
9992            serde_json::json!("9007199254740993.123456789")
9993        );
9994        assert!(
9995            normalize_entity_key_value("users", &column, &serde_json::json!("not-a-number"))
9996                .is_err()
9997        );
9998    }
9999
10000    #[test]
10001    fn federation_integer_backed_ids_have_one_normal_form() {
10002        let column = entity_key_column("int4", true);
10003
10004        let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
10005        let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
10006        assert_eq!(number, serde_json::json!(2));
10007        assert_eq!(string, number);
10008        assert!(normalize_entity_key_value("users", &column, &serde_json::json!("two")).is_err());
10009    }
10010
10011    #[test]
10012    fn federation_text_backed_ids_normalize_integer_input_to_text() {
10013        let column = entity_key_column("text", true);
10014
10015        let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
10016        let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
10017        assert_eq!(number, serde_json::json!("2"));
10018        assert_eq!(string, number);
10019    }
10020
10021    #[test]
10022    fn federation_uuid_backed_ids_are_validated_and_canonicalized() {
10023        let column = entity_key_column("uuid", true);
10024
10025        assert_eq!(
10026            normalize_entity_key_value(
10027                "users",
10028                &column,
10029                &serde_json::json!("550E8400-E29B-41D4-A716-446655440000")
10030            )
10031            .unwrap(),
10032            serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
10033        );
10034        assert!(
10035            normalize_entity_key_value("users", &column, &serde_json::json!("not-a-uuid")).is_err()
10036        );
10037    }
10038
10039    fn create_test_table(name: &str) -> Table {
10040        let mut columns = IndexMap::new();
10041        columns.insert(
10042            "id".into(),
10043            Column {
10044                name: "id".into(),
10045                description: None,
10046                nullable: false,
10047                data_type: "integer".into(),
10048                nominal_type: "int4".into(),
10049                max_len: None,
10050                default: Some("nextval('id_seq')".into()),
10051                enum_values: vec![],
10052                is_pk: true,
10053                position: 1,
10054                always_generated: false,
10055                domain_type: None,
10056            },
10057        );
10058        columns.insert(
10059            "name".into(),
10060            Column {
10061                name: "name".into(),
10062                description: None,
10063                nullable: false,
10064                data_type: "text".into(),
10065                nominal_type: "text".into(),
10066                max_len: None,
10067                default: None,
10068                enum_values: vec![],
10069                is_pk: false,
10070                position: 2,
10071                always_generated: false,
10072                domain_type: None,
10073            },
10074        );
10075
10076        Table {
10077            schema: "public".into(),
10078            name: name.into(),
10079            description: None,
10080            is_view: false,
10081            insertable: true,
10082            updatable: true,
10083            deletable: true,
10084            pk_cols: vec!["id".into()],
10085            unique_constraints: Vec::new(),
10086            columns,
10087            computed_columns: Default::default(),
10088            is_partitioned: false,
10089        }
10090    }
10091
10092    fn create_test_schema_cache() -> SchemaCache {
10093        let mut tables = HashMap::new();
10094        let users = create_test_table("users");
10095        tables.insert(users.qualified_identifier(), users);
10096
10097        SchemaCache {
10098            tables,
10099            relationships: HashMap::new(),
10100            routines: HashMap::new(),
10101            timezones: HashSet::new(),
10102            media_handlers: HashMap::new(),
10103            pg_version: 150000,
10104            representations: Default::default(),
10105        }
10106    }
10107
10108    fn add_test_table(cache: &mut SchemaCache, name: &str) {
10109        let table = create_test_table(name);
10110        cache.tables.insert(table.qualified_identifier(), table);
10111    }
10112
10113    fn add_test_posts_relationship(cache: &mut SchemaCache) {
10114        let users = QualifiedIdentifier::new("public", "users");
10115        cache.relationships.insert(
10116            (users.clone(), "public".into()),
10117            vec![Relationship::ForeignKey {
10118                table: users,
10119                foreign_table: QualifiedIdentifier::new("public", "posts"),
10120                is_self: false,
10121                cardinality: Cardinality::O2M {
10122                    constraint: "posts_user_id_fkey".into(),
10123                    columns: vec![("id".into(), "user_id".into())],
10124                },
10125                table_is_view: false,
10126                foreign_table_is_view: false,
10127                constraint_name: "posts_user_id_fkey".into(),
10128            }],
10129        );
10130    }
10131
10132    // ============================================================================
10133    // Type Reference Tests
10134    // ============================================================================
10135
10136    #[test]
10137    fn test_graphql_type_ref_simple() {
10138        let _type_ref = graphql_type_ref("String");
10139        // TypeRef doesn't implement PartialEq, so we just test it doesn't panic
10140    }
10141
10142    #[test]
10143    fn test_graphql_type_ref_non_null() {
10144        let _type_ref = graphql_type_ref("String!");
10145    }
10146
10147    #[test]
10148    fn test_graphql_type_ref_list() {
10149        let _type_ref = graphql_type_ref("[String]");
10150    }
10151
10152    #[test]
10153    fn test_graphql_type_ref_list_non_null() {
10154        let _type_ref = graphql_type_ref("[String!]!");
10155    }
10156
10157    // ============================================================================
10158    // Value Conversion Tests
10159    // ============================================================================
10160
10161    #[test]
10162    fn test_value_to_json_null() {
10163        let value = Value::Null;
10164        let json = value_to_json(&value);
10165        assert_eq!(json, serde_json::Value::Null);
10166    }
10167
10168    #[test]
10169    fn test_value_to_json_boolean() {
10170        let value = Value::Boolean(true);
10171        let json = value_to_json(&value);
10172        assert_eq!(json, serde_json::Value::Bool(true));
10173    }
10174
10175    #[test]
10176    fn test_value_to_json_number() {
10177        let value = Value::Number(42.into());
10178        let json = value_to_json(&value);
10179        assert_eq!(json, serde_json::json!(42));
10180    }
10181
10182    #[test]
10183    fn test_value_to_json_string() {
10184        let value = Value::String("hello".to_string());
10185        let json = value_to_json(&value);
10186        assert_eq!(json, serde_json::Value::String("hello".to_string()));
10187    }
10188
10189    #[test]
10190    fn test_value_to_json_list() {
10191        let value = Value::List(vec![Value::Number(1.into()), Value::Number(2.into())]);
10192        let json = value_to_json(&value);
10193        assert_eq!(json, serde_json::json!([1, 2]));
10194    }
10195
10196    #[test]
10197    fn test_json_to_value_null() {
10198        let json = serde_json::Value::Null;
10199        let value = json_to_value(json);
10200        assert!(matches!(value, Value::Null));
10201    }
10202
10203    #[test]
10204    fn test_json_to_value_boolean() {
10205        let json = serde_json::Value::Bool(false);
10206        let value = json_to_value(json);
10207        assert!(matches!(value, Value::Boolean(false)));
10208    }
10209
10210    #[test]
10211    fn test_json_to_value_number() {
10212        let json = serde_json::json!(123);
10213        let value = json_to_value(json);
10214        assert!(matches!(value, Value::Number(_)));
10215    }
10216
10217    #[test]
10218    fn test_json_to_value_string() {
10219        let json = serde_json::Value::String("test".to_string());
10220        let value = json_to_value(json);
10221        assert!(matches!(value, Value::String(_)));
10222    }
10223
10224    #[test]
10225    fn test_json_to_value_array() {
10226        let json = serde_json::json!([1, 2, 3]);
10227        let value = json_to_value(json);
10228        assert!(matches!(value, Value::List(_)));
10229    }
10230
10231    #[test]
10232    fn test_json_to_value_object() {
10233        let json = serde_json::json!({"key": "value"});
10234        let value = json_to_value(json);
10235        assert!(matches!(value, Value::Object(_)));
10236    }
10237
10238    // ============================================================================
10239    // Schema Building Tests
10240    // ============================================================================
10241
10242    #[test]
10243    fn test_build_dynamic_schema() {
10244        let cache = create_test_schema_cache();
10245        let config = SchemaConfig::default();
10246        let generated = build_schema(&cache, &config);
10247
10248        let result = build_dynamic_schema(
10249            &generated,
10250            &cache,
10251            None,
10252            None,
10253            Arc::new(Default::default()),
10254            std::time::Duration::from_secs(30),
10255            None,
10256        );
10257        if let Err(ref e) = result {
10258            eprintln!("Schema build error: {:?}", e);
10259        }
10260        assert!(result.is_ok(), "Schema build failed: {:?}", result.err());
10261    }
10262
10263    #[test]
10264    fn a_column_type_override_reaches_fields_and_key_arguments() {
10265        let cache = create_test_schema_cache();
10266        let names = crate::names::NameOverrides::parse(
10267            r#"{"tables": {"public.users": {"column_types": {"id": "ID"}}}}"#,
10268        )
10269        .unwrap();
10270        let config = SchemaConfig {
10271            names: names.clone(),
10272            ..SchemaConfig::default()
10273        };
10274        let generated = build_schema(&cache, &config);
10275        let schema = build_dynamic_schema(
10276            &generated,
10277            &cache,
10278            None,
10279            None,
10280            Arc::new(names),
10281            std::time::Duration::from_secs(30),
10282            None,
10283        )
10284        .expect("schema with an ID override should build");
10285        let sdl = schema.sdl();
10286
10287        let input_body = |name: &str| {
10288            sdl.split_once(&format!("input {name} {{"))
10289                .and_then(|(_, rest)| rest.split_once('}'))
10290                .map(|(body, _)| body.to_string())
10291                .unwrap_or_else(|| panic!("missing input {name}:\n{sdl}"))
10292        };
10293
10294        assert!(sdl.contains("id: ID!"), "object and input fields:\n{sdl}");
10295        assert!(
10296            sdl.contains("users_by_pk(id: ID!): users"),
10297            "query key argument:\n{sdl}"
10298        );
10299        assert!(
10300            sdl.contains("delete_users_by_pk(id: ID!): users"),
10301            "delete key argument:\n{sdl}"
10302        );
10303        assert!(
10304            input_body("users_pk_columns_input").contains("id: ID!"),
10305            "update key input:\n{sdl}"
10306        );
10307        assert!(
10308            input_body("users_bool_exp").contains("id: ID_comparison_exp"),
10309            "comparison input:\n{sdl}"
10310        );
10311        assert!(
10312            input_body("users_insert_input").contains("id: ID"),
10313            "write input:\n{sdl}"
10314        );
10315    }
10316
10317    #[test]
10318    fn a_type_prefix_names_the_complete_dynamic_schema() {
10319        let cache = create_test_schema_cache();
10320        let config = SchemaConfig {
10321            type_prefix: Some("test".into()),
10322            ..SchemaConfig::default()
10323        };
10324        let generated = build_schema(&cache, &config);
10325        let schema = build_dynamic_schema(
10326            &generated,
10327            &cache,
10328            None,
10329            None,
10330            Arc::new(Default::default()),
10331            std::time::Duration::from_secs(30),
10332            None,
10333        )
10334        .expect("a prefixed schema should build");
10335        let sdl = schema.sdl();
10336
10337        assert!(sdl.contains("type test_users "), "object type:\n{sdl}");
10338        assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10339        assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10340        assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10341        assert!(
10342            sdl.contains("test_users_aggregate"),
10343            "aggregate type:\n{sdl}"
10344        );
10345        assert!(sdl.contains("test_insert_users("), "mutation root:\n{sdl}");
10346    }
10347
10348    #[test]
10349    fn federation_is_absent_by_default() {
10350        let cache = create_test_schema_cache();
10351        let generated = build_schema(&cache, &SchemaConfig::default());
10352        let schema = build_dynamic_schema(
10353            &generated,
10354            &cache,
10355            None,
10356            None,
10357            Arc::new(Default::default()),
10358            std::time::Duration::from_secs(30),
10359            None,
10360        )
10361        .expect("a non-federated schema should build");
10362        let sdl = schema.sdl();
10363
10364        assert!(!sdl.contains("_Service"), "service type:\n{sdl}");
10365        assert!(!sdl.contains("_service"), "service field:\n{sdl}");
10366        assert!(!sdl.contains("@key"), "federation key:\n{sdl}");
10367    }
10368
10369    #[test]
10370    fn federation_entity_selections_respect_fragment_type_conditions() {
10371        let document = async_graphql::parser::parse_query(
10372            r#"
10373            query {
10374              _entities(representations: []) {
10375                __typename
10376                ... on authors { id items { title } }
10377                ...ShopFields
10378                ... { common }
10379                ...EntityName
10380              }
10381            }
10382            fragment ShopFields on shops { id items { sku } }
10383            fragment EntityName on _Entity { __typename }
10384            "#,
10385        )
10386        .expect("query parses");
10387        let (_, operation) = document.operations.iter().next().expect("one operation");
10388        let entity = operation
10389            .node
10390            .selection_set
10391            .node
10392            .items
10393            .iter()
10394            .find_map(|selection| match &selection.node {
10395                Selection::Field(field) if field.node.name.node == "_entities" => Some(&field.node),
10396                _ => None,
10397            })
10398            .expect("_entities field");
10399
10400        assert_eq!(
10401            entity_field_applicability(&entity.selection_set.node, &document.fragments, "authors"),
10402            vec![true, true, true, false, false, true, true]
10403        );
10404        assert_eq!(
10405            entity_field_applicability(&entity.selection_set.node, &document.fragments, "shops"),
10406            vec![true, false, false, true, true, true, true]
10407        );
10408    }
10409
10410    #[tokio::test]
10411    async fn federation_entity_lists_allow_null_members() {
10412        let entity =
10413            Object::new("widgets").field(Field::new("id", TypeRef::named_nn(TypeRef::INT), |_| {
10414                FieldFuture::new(async { Ok(Some(Value::from(2))) })
10415            }));
10416        let union = Union::new("_Entity").possible_type("widgets");
10417        let query = Object::new("Query").field(Field::new(
10418            "_entities",
10419            TypeRef::named_list_nn("_Entity"),
10420            |_| {
10421                FieldFuture::new(async {
10422                    Ok(Some(FieldValue::list([
10423                        FieldValue::NULL,
10424                        FieldValue::NULL.with_type("widgets"),
10425                    ])))
10426                })
10427            },
10428        ));
10429        let schema = Schema::build("Query", None, None)
10430            .extension(NullableFederationEntity)
10431            .register(entity)
10432            .register(union)
10433            .register(query)
10434            .finish()
10435            .expect("schema builds");
10436
10437        let response = schema
10438            .execute("{ _entities { ... on widgets { id } } }")
10439            .await;
10440
10441        assert!(response.errors.is_empty(), "{:?}", response.errors);
10442        assert_eq!(
10443            response.data,
10444            async_graphql::value!({ "_entities": [null, { "id": 2 }] })
10445        );
10446    }
10447
10448    #[test]
10449    fn federation_metadata_is_byte_for_byte_inert_when_disabled() {
10450        let cache = create_test_schema_cache();
10451        let sdl_for = |config: SchemaConfig| {
10452            let generated = build_schema(&cache, &config);
10453            build_dynamic_schema(
10454                &generated,
10455                &cache,
10456                None,
10457                None,
10458                Arc::new(config.names.clone()),
10459                std::time::Duration::from_secs(30),
10460                None,
10461            )
10462            .expect("schema should build")
10463            .sdl()
10464        };
10465
10466        let baseline = sdl_for(SchemaConfig {
10467            type_prefix: Some("test".into()),
10468            ..SchemaConfig::default()
10469        });
10470        let with_disabled_federation_metadata = sdl_for(SchemaConfig {
10471            enable_federation: false,
10472            type_prefix: Some("test".into()),
10473            names: crate::names::NameOverrides::parse(
10474                r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10475            )
10476            .expect("metadata"),
10477            ..SchemaConfig::default()
10478        });
10479
10480        assert_eq!(with_disabled_federation_metadata, baseline);
10481        assert!(
10482            with_disabled_federation_metadata.contains("type test_users "),
10483            "prefixed object type remains when federation is disabled:\n{with_disabled_federation_metadata}"
10484        );
10485        assert!(
10486            !with_disabled_federation_metadata.contains("_Service"),
10487            "service type:\n{with_disabled_federation_metadata}"
10488        );
10489        assert!(
10490            !with_disabled_federation_metadata.contains("@key"),
10491            "federation key:\n{with_disabled_federation_metadata}"
10492        );
10493    }
10494
10495    #[test]
10496    fn shared_entities_make_relationship_fields_shareable() {
10497        let federation_sdl = |shared: bool| {
10498            let mut cache = create_test_schema_cache();
10499            add_test_table(&mut cache, "posts");
10500            add_test_posts_relationship(&mut cache);
10501            let names = match shared {
10502                true => crate::names::NameOverrides::parse(
10503                    r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10504                )
10505                .expect("metadata"),
10506                false => crate::names::NameOverrides::default(),
10507            };
10508            let config = SchemaConfig {
10509                enable_federation: true,
10510                type_prefix: Some("test".into()),
10511                names: names.clone(),
10512                ..SchemaConfig::default()
10513            };
10514            let generated = build_schema(&cache, &config);
10515            build_dynamic_schema(
10516                &generated,
10517                &cache,
10518                None,
10519                None,
10520                Arc::new(names),
10521                std::time::Duration::from_secs(30),
10522                None,
10523            )
10524            .expect("federated schema should build")
10525            .sdl_with_options(async_graphql::SDLExportOptions::new().federation())
10526        };
10527
10528        let shared = federation_sdl(true);
10529        assert!(
10530            shared.contains("): [test_posts!]! @shareable"),
10531            "shared relationship field:\n{shared}"
10532        );
10533        assert!(
10534            shared.contains("): test_posts_aggregate! @shareable"),
10535            "shared relationship aggregate field:\n{shared}"
10536        );
10537
10538        let not_shared = federation_sdl(false);
10539        assert!(
10540            not_shared.contains("): [test_posts!]!"),
10541            "non-shared relationship field:\n{not_shared}"
10542        );
10543        assert!(
10544            !not_shared.contains("): [test_posts!]! @shareable"),
10545            "non-shared relationship field must not be shareable:\n{not_shared}"
10546        );
10547        assert!(
10548            not_shared.contains("): test_posts_aggregate!"),
10549            "non-shared relationship aggregate field:\n{not_shared}"
10550        );
10551        assert!(
10552            !not_shared.contains("): test_posts_aggregate! @shareable"),
10553            "non-shared relationship aggregate must not be shareable:\n{not_shared}"
10554        );
10555    }
10556
10557    #[tokio::test]
10558    async fn federation_exposes_service_sdl_and_entity_keys() {
10559        let mut cache = create_test_schema_cache();
10560        add_test_table(&mut cache, "posts");
10561        let config = SchemaConfig {
10562            enable_federation: true,
10563            type_prefix: Some("test".into()),
10564            names: crate::names::NameOverrides::parse(
10565                r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10566            )
10567            .expect("metadata"),
10568            ..SchemaConfig::default()
10569        };
10570        let generated = build_schema(&cache, &config);
10571        let schema = build_dynamic_schema(
10572            &generated,
10573            &cache,
10574            None,
10575            None,
10576            Arc::new(Default::default()),
10577            std::time::Duration::from_secs(30),
10578            None,
10579        )
10580        .expect("a federated schema should build");
10581        let sdl = schema.sdl();
10582        let federation_sdl =
10583            schema.sdl_with_options(async_graphql::SDLExportOptions::new().federation());
10584
10585        assert!(sdl.contains("_Service"), "service type:\n{sdl}");
10586        assert!(sdl.contains("_service"), "service field:\n{sdl}");
10587        assert!(
10588            federation_sdl.contains("type users @key(fields: \"id\")"),
10589            "users key:\n{federation_sdl}"
10590        );
10591        assert!(
10592            federation_sdl.contains("@shareable"),
10593            "shared field directive:\n{federation_sdl}"
10594        );
10595        assert!(sdl.contains("type users "), "shared object type:\n{sdl}");
10596        assert!(
10597            !sdl.contains("type test_users "),
10598            "prefixed shared object type:\n{sdl}"
10599        );
10600        assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10601        assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10602        assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10603        assert!(
10604            !sdl.contains("input users_bool_exp "),
10605            "unprefixed derived input:\n{sdl}"
10606        );
10607        assert!(
10608            sdl.contains("test_users_aggregate"),
10609            "aggregate type:\n{sdl}"
10610        );
10611        assert!(
10612            !sdl.contains("type users_aggregate "),
10613            "unprefixed aggregate type:\n{sdl}"
10614        );
10615        assert!(
10616            sdl.contains("test_users_mutation_response"),
10617            "mutation response type:\n{sdl}"
10618        );
10619        assert!(
10620            !sdl.contains("type users_mutation_response "),
10621            "unprefixed mutation response type:\n{sdl}"
10622        );
10623        assert!(
10624            sdl.contains("type test_posts {"),
10625            "non-shared object type remains prefixed:\n{sdl}"
10626        );
10627        assert!(
10628            !sdl.contains("type posts {"),
10629            "non-shared object type must not become unprefixed:\n{sdl}"
10630        );
10631        assert!(sdl.contains("test_posts("), "prefixed posts root:\n{sdl}");
10632        assert!(
10633            sdl.contains("test_posts_bool_exp"),
10634            "prefixed posts bool_exp:\n{sdl}"
10635        );
10636
10637        let response = schema.execute("{ _service { sdl } }").await;
10638        assert!(
10639            response.errors.is_empty(),
10640            "service query errors: {:?}",
10641            response.errors
10642        );
10643        let service = response.data.into_json().expect("service response JSON");
10644        let service_sdl = service
10645            .get("_service")
10646            .and_then(|value| value.get("sdl"))
10647            .and_then(serde_json::Value::as_str)
10648            .expect("service SDL string");
10649        assert!(
10650            service_sdl.contains("type Query"),
10651            "federation root type:\n{service_sdl}"
10652        );
10653        assert!(
10654            service_sdl.contains("test_users("),
10655            "query fields are on the federation root:\n{service_sdl}"
10656        );
10657        assert!(
10658            service_sdl.contains("test_posts("),
10659            "non-shared query fields are on the federation root:\n{service_sdl}"
10660        );
10661        assert!(
10662            !service_sdl.contains("type query_root"),
10663            "Apollo composition treats query_root as an ordinary object without a schema definition:\n{service_sdl}"
10664        );
10665        assert!(
10666            service_sdl.contains("type users @key(fields: \"id\")"),
10667            "{service_sdl}"
10668        );
10669        assert!(
10670            service_sdl.contains("type test_posts @key(fields: \"id\")"),
10671            "{service_sdl}"
10672        );
10673    }
10674
10675    /// `_exists` becomes a subselect over the table it names, correlated with
10676    /// nothing -- which is the difference between it and a relationship.
10677    #[test]
10678    fn exists_is_a_subselect_over_another_table() {
10679        let cache = create_test_schema_cache();
10680        let names = crate::names::NameOverrides::default();
10681        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10682        let scope = WhereScope::table("public", "users", "users", &names)
10683            .with_resolution(&cache, &relationships);
10684
10685        let mut param_idx = 1usize;
10686        let mut values: Vec<serde_json::Value> = Vec::new();
10687        let mut aliases = 0usize;
10688        let sql = build_condition(
10689            &serde_json::json!({
10690                "_exists": {"_table": "users", "_where": {"name": {"_eq": "Ann"}}}
10691            }),
10692            &scope,
10693            &mut param_idx,
10694            &mut values,
10695            &mut aliases,
10696        )
10697        .expect("compiles")
10698        .expect("constrains something");
10699
10700        assert_eq!(
10701            sql,
10702            "EXISTS (SELECT 1 FROM \"public\".\"users\" AS \"pgrst_exists_1\" \
10703             WHERE \"pgrst_exists_1\".\"name\" = $1::text)"
10704        );
10705        assert_eq!(values, vec![serde_json::json!("Ann")]);
10706    }
10707
10708    /// A `_table` nobody has is refused rather than written into the query.
10709    #[test]
10710    fn exists_names_a_table_the_server_has() {
10711        let cache = create_test_schema_cache();
10712        let names = crate::names::NameOverrides::default();
10713        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10714        let scope = WhereScope::table("public", "users", "users", &names)
10715            .with_resolution(&cache, &relationships);
10716        let error = build_condition(
10717            &serde_json::json!({"_exists": {"_table": "nowhere", "_where": {}}}),
10718            &scope,
10719            &mut 1,
10720            &mut Vec::new(),
10721            &mut 0,
10722        )
10723        .expect_err("a table that is not there is an error");
10724        assert!(
10725            error.message.contains("public.nowhere"),
10726            "{}",
10727            error.message
10728        );
10729    }
10730
10731    /// A role granted "how many" and not "which" gets the count and no rows.
10732    ///
10733    /// A select permission naming no columns, with `allow_aggregations`, is
10734    /// Hasura's way of writing that. The table has an aggregate root and no
10735    /// row type, so `nodes` is not there to be a list of one -- and `count`
10736    /// takes no `columns`, because there are none to name.
10737    #[test]
10738    fn a_table_a_role_may_count_and_not_read_has_a_count_and_no_rows() {
10739        let cache = create_test_schema_cache();
10740        let names = crate::names::NameOverrides::parse(
10741            r#"{"tables": {"public.users": {"permissions":
10742                 {"counter": {"select": {"columns": [], "filter": {},
10743                                         "allow_aggregations": true}}}}}}"#,
10744        )
10745        .unwrap();
10746        let view = crate::role::cache_for_role(&cache, &names, "counter", false);
10747        assert!(!view.tables.is_empty(), "the table is there to be counted");
10748
10749        let config = SchemaConfig {
10750            names: names.clone(),
10751            role: Some("counter".to_string()),
10752            ..SchemaConfig::default()
10753        };
10754        let generated = build_schema(&view, &config);
10755        let schema = build_dynamic_schema(
10756            &generated,
10757            &view,
10758            None,
10759            None,
10760            Arc::new(names),
10761            std::time::Duration::from_secs(30),
10762            Some("counter"),
10763        )
10764        .expect("a role that may only count still has a buildable schema");
10765        let sdl = schema.sdl();
10766
10767        assert!(
10768            sdl.contains("users_aggregate"),
10769            "the count is there:\n{}",
10770            sdl
10771        );
10772        assert!(sdl.contains("count: Int!"), "and it is a count:\n{}", sdl);
10773        // No row type, so nothing that would answer with one.
10774        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10775        assert!(
10776            !sdl.contains("nodes"),
10777            "and no rows beside the numbers:\n{}",
10778            sdl
10779        );
10780        assert!(
10781            !sdl.contains("users_select_column"),
10782            "no column enum to order by or count over:\n{}",
10783            sdl
10784        );
10785        // The functions that read column data are absent with the columns.
10786        assert!(!sdl.contains("users_max_fields"), "no max:\n{}", sdl);
10787    }
10788
10789    /// A role granted `insert` and no `select` gets the write and no type.
10790    ///
10791    /// Hasura's own corpus does this eight times over. What has to hold is
10792    /// that the schema still builds: `insert_users` is there, `users` is not,
10793    /// and the response the insert answers with has no `returning` to name a
10794    /// type that does not exist.
10795    #[test]
10796    fn a_table_a_role_may_only_write_gets_the_write_and_no_type() {
10797        let cache = create_test_schema_cache();
10798        let names = crate::names::NameOverrides::parse(
10799            r#"{"tables": {"public.users": {"permissions":
10800                 {"writer": {"insert": {"columns": "*", "check": {}}}}}}}"#,
10801        )
10802        .unwrap();
10803        let view = crate::role::cache_for_role(&cache, &names, "writer", false);
10804        let config = SchemaConfig {
10805            names: names.clone(),
10806            role: Some("writer".to_string()),
10807            ..SchemaConfig::default()
10808        };
10809        let generated = build_schema(&view, &config);
10810
10811        let schema = build_dynamic_schema(
10812            &generated,
10813            &view,
10814            None,
10815            None,
10816            Arc::new(names),
10817            std::time::Duration::from_secs(30),
10818            Some("writer"),
10819        )
10820        .expect("a role that may only write still has a buildable schema");
10821        let sdl = schema.sdl();
10822
10823        assert!(
10824            sdl.contains("insert_users("),
10825            "the write is there:\n{}",
10826            sdl
10827        );
10828        assert!(
10829            sdl.contains("users_insert_input"),
10830            "and its input:\n{}",
10831            sdl
10832        );
10833        // No row type, and so none of the three fields that answer with one.
10834        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10835        assert!(!sdl.contains("insert_users_one"), "no insert_one:\n{}", sdl);
10836        assert!(
10837            !sdl.contains("returning"),
10838            "and nothing to return:\n{}",
10839            sdl
10840        );
10841        assert!(
10842            sdl.contains("users_mutation_response"),
10843            "the count is still answered:\n{}",
10844            sdl
10845        );
10846        assert!(
10847            !sdl.contains("_mutation_response_mutation_response"),
10848            "and no type is named twice over:\n{}",
10849            sdl
10850        );
10851    }
10852
10853    /// A cache with one VOLATILE function returning rows of `users`, which is
10854    /// the shape `add_to_score` has in Hasura's corpus.
10855    fn cache_with_a_mutation_function() -> SchemaCache {
10856        use postrust_core::schema_cache::{FuncVolatility, RetType, Routine};
10857
10858        let mut cache = create_test_schema_cache();
10859        let routine = Routine {
10860            schema: "public".into(),
10861            name: "add_to_score".into(),
10862            description: None,
10863            params: Vec::new(),
10864            return_type: RetType::SetOf("users".into()),
10865            returns_composite: true,
10866            volatility: FuncVolatility::Volatile,
10867            has_variadic: false,
10868            isolation_level: None,
10869            settings: Vec::new(),
10870            is_procedure: false,
10871            media_type: None,
10872            media_base_type: None,
10873            output_columns: Vec::new(),
10874        };
10875        cache
10876            .routines
10877            .insert(routine.qualified_identifier(), vec![routine]);
10878        cache
10879    }
10880
10881    /// A function with side effects is not granted by permission to read the
10882    /// table it returns. Hasura infers a query function's permission from
10883    /// that select and infers nothing for a mutation, so a role never named
10884    /// by a function permission has no mutation root at all.
10885    #[test]
10886    fn a_mutation_function_needs_the_role_to_be_named() {
10887        let cache = cache_with_a_mutation_function();
10888        let withheld = crate::names::NameOverrides::parse(
10889            r#"{"tables": {"public.users": {"permissions":
10890                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10891                "functions": {"public.add_to_score": {"exposed_as": "mutation"}}}"#,
10892        )
10893        .unwrap();
10894        let granted = crate::names::NameOverrides::parse(
10895            r#"{"tables": {"public.users": {"permissions":
10896                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10897                "functions": {"public.add_to_score":
10898                  {"exposed_as": "mutation", "roles": ["reader"]}}}"#,
10899        )
10900        .unwrap();
10901
10902        let sdl_for = |names: &crate::names::NameOverrides| {
10903            let view = crate::role::cache_for_role(&cache, names, "reader", false);
10904            let config = SchemaConfig {
10905                names: names.clone(),
10906                role: Some("reader".to_string()),
10907                ..SchemaConfig::default()
10908            };
10909            let generated = build_schema(&view, &config);
10910            build_dynamic_schema(
10911                &generated,
10912                &view,
10913                None,
10914                None,
10915                Arc::new(names.clone()),
10916                std::time::Duration::from_secs(30),
10917                Some("reader"),
10918            )
10919            .expect("schema builds")
10920            .sdl()
10921        };
10922
10923        let without = sdl_for(&withheld);
10924        assert!(
10925            !without.contains("add_to_score"),
10926            "not granted, so not there:\n{}",
10927            without
10928        );
10929        // And with no mutation of any kind left, there is no mutation root --
10930        // which is what `no mutations exist` is answered from.
10931        assert!(
10932            !without.contains("mutation: mutation_root"),
10933            "and no mutation root at all:\n{}",
10934            without
10935        );
10936
10937        let with = sdl_for(&granted);
10938        assert!(
10939            with.contains("add_to_score"),
10940            "named by a function permission, so it is:\n{}",
10941            with
10942        );
10943    }
10944
10945    /// An unrecognised role is a role that was granted nothing, not an error.
10946    #[test]
10947    fn a_role_the_document_never_names_is_granted_nothing() {
10948        let cache = Arc::new(create_test_schema_cache());
10949        let config = SchemaConfig {
10950            names: crate::names::NameOverrides::parse(
10951                r#"{"tables": {"public.users": {"permissions":
10952                     {"reader": {"select": {"columns": "*", "filter": {}}}}}}}"#,
10953            )
10954            .unwrap(),
10955            ..SchemaConfig::default()
10956        };
10957        let empty = build_ungranted_schema(&cache, &config).expect("it builds");
10958        let sdl = empty.sdl();
10959        assert!(
10960            sdl.contains("no_queries_available"),
10961            "nothing to read:\n{}",
10962            sdl
10963        );
10964        assert!(!sdl.contains("type users "), "and no table:\n{}", sdl);
10965        // A document with no permissions at all has no unrecognised roles.
10966        assert!(build_ungranted_schema(&cache, &SchemaConfig::default()).is_none());
10967    }
10968
10969    /// A column the role may write and not read is in the input and not in the
10970    /// type. The two column sets are the point: one schema cache, two answers.
10971    #[test]
10972    fn a_column_a_role_may_set_without_seeing_is_in_the_input_alone() {
10973        let cache = create_test_schema_cache();
10974        let names = crate::names::NameOverrides::parse(
10975            r#"{"tables": {"public.users": {"permissions":
10976                 {"user": {"select": {"columns": ["id"], "filter": {}},
10977                           "insert": {"columns": "*", "check": {}}}}}}}"#,
10978        )
10979        .unwrap();
10980        let view = crate::role::cache_for_role(&cache, &names, "user", false);
10981        let config = SchemaConfig {
10982            names: names.clone(),
10983            role: Some("user".to_string()),
10984            ..SchemaConfig::default()
10985        };
10986        let generated = build_schema(&view, &config);
10987        let object = &generated.object_types["users"];
10988        assert_eq!(
10989            object
10990                .fields
10991                .iter()
10992                .map(|f| f.name.as_str())
10993                .collect::<Vec<_>>(),
10994            vec!["id"],
10995            "the type shows what may be read"
10996        );
10997        assert!(
10998            object.writable_fields.iter().any(|f| f.name == "name"),
10999            "and the input keeps what may be written"
11000        );
11001
11002        let schema = build_dynamic_schema(
11003            &generated,
11004            &view,
11005            None,
11006            None,
11007            Arc::new(names),
11008            std::time::Duration::from_secs(30),
11009            Some("user"),
11010        )
11011        .expect("schema builds");
11012        let sdl = schema.sdl();
11013        let input = sdl
11014            .split("input users_insert_input")
11015            .nth(1)
11016            .and_then(|rest| rest.split('}').next())
11017            .expect("the insert input is registered");
11018        assert!(input.contains("name:"), "settable without being readable");
11019    }
11020
11021    /// A column PostgreSQL generates always is in the row type and in no
11022    /// write input. It is not a permission -- no role may name it -- so the
11023    /// refusal belongs to the schema rather than to the database.
11024    #[test]
11025    fn a_generated_column_is_readable_and_not_writable() {
11026        let mut cache = create_test_schema_cache();
11027        cache
11028            .tables
11029            .get_mut(&postrust_core::api_request::QualifiedIdentifier::new(
11030                "public", "users",
11031            ))
11032            .expect("the fixture has users")
11033            .columns
11034            .get_mut("id")
11035            .expect("with an id")
11036            .always_generated = true;
11037
11038        let names = crate::names::NameOverrides::default();
11039        let config = SchemaConfig {
11040            names: names.clone(),
11041            ..SchemaConfig::default()
11042        };
11043        let generated = build_schema(&cache, &config);
11044        let schema = build_dynamic_schema(
11045            &generated,
11046            &cache,
11047            None,
11048            None,
11049            Arc::new(names),
11050            std::time::Duration::from_secs(30),
11051            None,
11052        )
11053        .expect("schema builds");
11054        let sdl = schema.sdl();
11055
11056        let block = |header: &str| {
11057            sdl.split(header)
11058                .nth(1)
11059                .and_then(|rest| rest.split('}').next())
11060                .unwrap_or_else(|| panic!("{} is registered:\n{}", header, sdl))
11061                .to_string()
11062        };
11063        assert!(
11064            block("type users ").contains("id:"),
11065            "still readable:\n{}",
11066            sdl
11067        );
11068        for header in ["input users_insert_input", "input users_set_input"] {
11069            assert!(
11070                !block(header).contains("id:"),
11071                "{} may not name it:\n{}",
11072                header,
11073                block(header)
11074            );
11075        }
11076        // The table's only number was the generated column, so there is
11077        // nothing left to add to and no type for adding to it.
11078        assert!(
11079            !sdl.contains("users_inc_input"),
11080            "and nothing is left to increment:\n{}",
11081            sdl
11082        );
11083        assert!(
11084            block("input users_insert_input").contains("name:"),
11085            "and the rest of the table is untouched",
11086        );
11087    }
11088
11089    #[test]
11090    fn test_create_object_type() {
11091        let table = create_test_table("users");
11092        let obj = TableObjectType::from_table(&table);
11093        let _gql_obj = create_object_type(&obj, &[], None);
11094    }
11095
11096    #[test]
11097    fn test_create_query_type() {
11098        let cache = create_test_schema_cache();
11099        let config = SchemaConfig::default();
11100        let generated = build_schema(&cache, &config);
11101
11102        let _query = create_query_type(
11103            &generated,
11104            "query_root",
11105            None,
11106            Arc::new(HashMap::new()),
11107            Arc::new(Default::default()),
11108        );
11109    }
11110
11111    #[test]
11112    fn test_create_mutation_type() {
11113        let cache = create_test_schema_cache();
11114        let config = SchemaConfig::default();
11115        let generated = build_schema(&cache, &config);
11116
11117        let _mutation = create_mutation_type(
11118            &generated,
11119            "mutation_root",
11120            Arc::new(HashMap::new()),
11121            Arc::new(HashMap::new()),
11122            Arc::new(Default::default()),
11123            None,
11124            None,
11125        );
11126    }
11127
11128    // ============================================================================
11129    // Scalar Tests
11130    // ============================================================================
11131
11132    #[test]
11133    fn a_scalar_is_named_the_way_a_client_declares_it() {
11134        use crate::types::{pg_type_to_graphql, GraphQLType};
11135
11136        // These names appear in the client's own queries -- `query ($x:
11137        // jsonb!)` names a type that has to exist under exactly that
11138        // spelling -- so they are asserted rather than left to the Display
11139        // impl.
11140        assert_eq!(pg_type_to_graphql("jsonb").to_string(), "jsonb");
11141        assert_eq!(pg_type_to_graphql("json").to_string(), "json");
11142        assert_eq!(pg_type_to_graphql("int8").to_string(), "bigint");
11143        assert_eq!(pg_type_to_graphql("numeric").to_string(), "numeric");
11144        assert_eq!(pg_type_to_graphql("timestamptz").to_string(), "timestamptz");
11145        assert_eq!(pg_type_to_graphql("timestamp").to_string(), "timestamp");
11146        assert_eq!(pg_type_to_graphql("uuid").to_string(), "uuid");
11147
11148        // A type this server knows nothing about keeps its own name, which is
11149        // how a PostGIS column and a database enum both become usable.
11150        assert_eq!(pg_type_to_graphql("geometry").to_string(), "geometry");
11151        assert_eq!(
11152            pg_type_to_graphql("colors_enum"),
11153            GraphQLType::Custom("colors_enum".to_string())
11154        );
11155
11156        // And the leaf of an array is what gets registered, not the array.
11157        assert_eq!(
11158            leaf_scalar_name(&pg_type_to_graphql("_text")),
11159            "String".to_string()
11160        );
11161    }
11162
11163    // ============================================================================
11164    // Filter Input Type Tests
11165    // ============================================================================
11166
11167    #[test]
11168    fn every_table_gets_a_boolean_expression() {
11169        let cache = create_test_schema_cache();
11170        let config = SchemaConfig::default();
11171        let generated = build_schema(&cache, &config);
11172
11173        let (inputs, _) = crate::input::bool_exp::build_inputs(
11174            &generated.object_types,
11175            &generated.relationship_fields,
11176        );
11177        let names: HashSet<String> = inputs.iter().map(|i| i.type_name().to_string()).collect();
11178
11179        for table in generated.object_types.keys() {
11180            let expected = format!("{}_bool_exp", table);
11181            assert!(
11182                names.contains(&expected),
11183                "no {} among {:?}",
11184                expected,
11185                names
11186            );
11187        }
11188        // The scalars the fixture's columns use.
11189        assert!(names.contains("String_comparison_exp"));
11190        assert!(names.contains("Int_comparison_exp"));
11191    }
11192
11193    #[test]
11194    fn a_schema_carrying_the_boolean_expressions_still_builds() {
11195        let cache = create_test_schema_cache();
11196        let config = SchemaConfig::default();
11197        let generated = build_schema(&cache, &config);
11198
11199        let schema = build_dynamic_schema(
11200            &generated,
11201            &cache,
11202            None,
11203            None,
11204            Arc::new(Default::default()),
11205            std::time::Duration::from_secs(30),
11206            None,
11207        );
11208        assert!(schema.is_ok(), "{:?}", schema.err());
11209
11210        let sdl = schema.unwrap().sdl();
11211        assert!(
11212            sdl.contains("_bool_exp"),
11213            "no boolean expressions in:\n{}",
11214            sdl
11215        );
11216        assert!(sdl.contains("_and"), "no _and in the generated expressions");
11217    }
11218
11219    // ============================================================================
11220    // Subscription Tests
11221    // ============================================================================
11222
11223    #[test]
11224    fn test_build_schema_with_subscriptions() {
11225        let cache = create_test_schema_cache();
11226        let config = SchemaConfig {
11227            enable_subscriptions: true,
11228            ..SchemaConfig::default()
11229        };
11230        let generated = build_schema(&cache, &config);
11231
11232        // Generate subscription fields
11233        let sub_fields = generate_subscription_fields(&cache, &generated);
11234        assert!(!sub_fields.is_empty(), "Should have subscription fields");
11235
11236        // Build schema with subscriptions
11237        let result = build_dynamic_schema(
11238            &generated,
11239            &cache,
11240            Some(&sub_fields),
11241            None,
11242            Arc::new(Default::default()),
11243            std::time::Duration::from_secs(30),
11244            None,
11245        );
11246        assert!(result.is_ok(), "Schema with subscriptions should build");
11247    }
11248
11249    #[test]
11250    fn test_subscription_field_generation() {
11251        let cache = create_test_schema_cache();
11252        let config = SchemaConfig::default();
11253        let generated = build_schema(&cache, &config);
11254
11255        let fields = generate_subscription_fields(&cache, &generated);
11256
11257        // Should have one subscription field for the users table
11258        assert_eq!(fields.len(), 1);
11259        assert_eq!(fields[0].name, "users");
11260        assert_eq!(fields[0].table_name, "users");
11261        assert_eq!(fields[0].channel_name(), "postrust_public_users");
11262    }
11263
11264    #[test]
11265    fn test_create_subscription_type() {
11266        let cache = create_test_schema_cache();
11267        let config = SchemaConfig::default();
11268        let generated = build_schema(&cache, &config);
11269        // A live query per queryable root: the subscription root mirrors the
11270        // query root, which is the whole contract.
11271        let _subscription = create_subscription_type(
11272            &generated,
11273            "subscription_root",
11274            None,
11275            Arc::new(Default::default()),
11276            Arc::new(Default::default()),
11277            std::time::Duration::from_secs(30),
11278        );
11279    }
11280}
11281
11282/// One step of a JSON path: a key of an object or an index of an array.
11283#[derive(Debug, PartialEq, Eq)]
11284enum PathStep {
11285    Key(String),
11286    Index(usize),
11287}
11288
11289/// Read one part of a document, by the path spelling Hasura accepts.
11290///
11291/// `column(path: "objs[0]['你好']")` -- an optional leading `$`, then keys
11292/// written bare or after a dot, and indices or quoted keys written in
11293/// brackets. A step that names something the document does not have is null,
11294/// which is what `#>` answers to the same path.
11295fn walk_json_path(value: &Value, path: &str) -> Result<Value, async_graphql::Error> {
11296    let mut at = value;
11297    for step in parse_json_path(path)? {
11298        let next = match (&step, at) {
11299            (PathStep::Key(key), Value::Object(map)) => map.get(key.as_str()),
11300            (PathStep::Index(index), Value::List(items)) => items.get(*index),
11301            // An index of an object or a key of an array reads nothing, and so
11302            // does anything asked of a scalar.
11303            _ => None,
11304        };
11305        match next {
11306            Some(next) => at = next,
11307            None => return Ok(Value::Null),
11308        }
11309    }
11310    Ok(at.clone())
11311}
11312
11313/// A path whose bracket never closes: written wrong rather than written oddly.
11314fn unclosed_bracket(path: &str) -> async_graphql::Error {
11315    let message = format!("\"{}\" is not a JSON path: a bracket is never closed", path);
11316    validation_error(&message)
11317}
11318
11319/// Parse the path spelling Hasura accepts into its steps.
11320///
11321/// Deliberately tolerant about separators, because the corpus writes the same
11322/// path four ways: `objs[0]["x"]`, `objs[0].["x"]`, `objs.[0].["x"]` and
11323/// `.objs[0]['x']` all name one thing. What is not tolerated is a bracket that
11324/// never closes, which is a path the client got wrong rather than a spelling.
11325fn parse_json_path(path: &str) -> Result<Vec<PathStep>, async_graphql::Error> {
11326    let mut steps = Vec::new();
11327    let mut chars = path.chars().peekable();
11328    // `$` is the document itself, and naming it is optional.
11329    if chars.peek() == Some(&'$') {
11330        chars.next();
11331    }
11332    while let Some(&c) = chars.peek() {
11333        match c {
11334            // A separator carries no step of its own.
11335            '.' => {
11336                chars.next();
11337            }
11338            '[' => {
11339                chars.next();
11340                let quote = match chars.peek() {
11341                    Some(&q @ ('\'' | '"')) => {
11342                        chars.next();
11343                        Some(q)
11344                    }
11345                    _ => None,
11346                };
11347                let mut inner = String::new();
11348                loop {
11349                    let Some(c) = chars.next() else {
11350                        return Err(unclosed_bracket(path));
11351                    };
11352                    match (quote, c) {
11353                        (Some(quote), c) if c == quote => break,
11354                        (None, ']') => break,
11355                        _ => inner.push(c),
11356                    }
11357                }
11358                // A quoted key's closing bracket, which the quote ended before.
11359                if quote.is_some() {
11360                    match chars.next() {
11361                        Some(']') => {}
11362                        _ => return Err(unclosed_bracket(path)),
11363                    }
11364                }
11365                // An unquoted number is an index; anything else is a key,
11366                // since an object may be keyed by a word as well as a digit.
11367                match (quote, inner.parse::<usize>()) {
11368                    (None, Ok(index)) => steps.push(PathStep::Index(index)),
11369                    _ => steps.push(PathStep::Key(inner)),
11370                }
11371            }
11372            _ => {
11373                let mut key = String::new();
11374                while let Some(&c) = chars.peek() {
11375                    if matches!(c, '.' | '[') {
11376                        break;
11377                    }
11378                    key.push(c);
11379                    chars.next();
11380                }
11381                steps.push(PathStep::Key(key));
11382            }
11383        }
11384    }
11385    Ok(steps)
11386}
11387
11388#[cfg(test)]
11389mod json_path_tests {
11390    use super::*;
11391
11392    fn steps(path: &str) -> Vec<PathStep> {
11393        parse_json_path(path).expect("the path parses")
11394    }
11395
11396    fn key(name: &str) -> PathStep {
11397        PathStep::Key(name.to_string())
11398    }
11399
11400    #[test]
11401    fn the_document_itself_has_no_steps() {
11402        assert_eq!(steps("$"), vec![]);
11403        assert_eq!(steps(""), vec![]);
11404    }
11405
11406    #[test]
11407    fn a_key_is_written_bare_or_after_a_dot() {
11408        assert_eq!(steps("a"), vec![key("a")]);
11409        assert_eq!(steps(".obj.c1"), vec![key("obj"), key("c1")]);
11410        assert_eq!(steps("._underscore"), vec![key("_underscore")]);
11411    }
11412
11413    #[test]
11414    fn a_bracket_holds_an_index_or_a_quoted_key() {
11415        assert_eq!(steps("arr[0]"), vec![key("arr"), PathStep::Index(0)]);
11416        assert_eq!(steps("['!@#$%^']"), vec![key("!@#$%^")]);
11417        assert_eq!(
11418            steps("translations['hello world!']"),
11419            vec![key("translations"), key("hello world!")]
11420        );
11421    }
11422
11423    /// The corpus writes one path four ways, and Hasura reads them all alike.
11424    #[test]
11425    fn a_dot_before_a_bracket_changes_nothing() {
11426        let expected = vec![key("objs"), PathStep::Index(0), key("\u{4f60}\u{597d}")];
11427        for spelling in [
11428            "objs[0]['\u{4f60}\u{597d}']",
11429            "objs[0][\"\u{4f60}\u{597d}\"]",
11430            "objs[0].[\"\u{4f60}\u{597d}\"]",
11431            "objs.[0].[\"\u{4f60}\u{597d}\"]",
11432        ] {
11433            assert_eq!(steps(spelling), expected, "{}", spelling);
11434        }
11435    }
11436
11437    #[test]
11438    fn a_bracket_that_never_closes_is_refused() {
11439        assert!(parse_json_path("arr[0").is_err());
11440        assert!(parse_json_path("['key'").is_err());
11441    }
11442
11443    #[test]
11444    fn a_step_the_document_does_not_have_reads_null() {
11445        let document = Value::from_json(serde_json::json!({
11446            "obj": {"c1": "c2"},
11447            "arr": [1, 2, 3],
11448        }))
11449        .expect("a document");
11450        let at = |path| walk_json_path(&document, path).expect("the path parses");
11451        assert_eq!(at(".obj.c1"), Value::String("c2".to_string()));
11452        assert_eq!(at("arr[1]"), Value::Number(2.into()));
11453        assert_eq!(at("arr[9]"), Value::Null);
11454        assert_eq!(at(".obj.nothing"), Value::Null);
11455        // An index of an object, and a key of an array: neither reads
11456        // anything, which is what `#>` answers.
11457        assert_eq!(at("obj[0]"), Value::Null);
11458        assert_eq!(at("arr.first"), Value::Null);
11459    }
11460}