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::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    /// Get the notification broker.
391    pub async fn get_broker(&self) -> Option<Arc<RwLock<Option<NotifyBroker>>>> {
392        Some(Arc::clone(&self.broker))
393    }
394}
395
396/// Handle a GraphQL request.
397pub async fn graphql_handler(
398    State(state): State<Arc<GraphQLState>>,
399    ctx: GraphQLContext,
400    req: GraphQLRequest,
401) -> GraphQLResponse {
402    let request = req
403        .into_inner()
404        .data(ctx)
405        .data(state.pool.clone())
406        .data(Arc::clone(&state.broker));
407    state.schema.execute(request).await.into()
408}
409
410/// Handle GraphQL WebSocket subscription upgrade.
411///
412/// This should be called with a WebSocket upgrade request to enable
413/// GraphQL subscriptions over WebSocket.
414///
415/// It authenticates nothing: no caller identity, session or JWT claims reach
416/// the operations it runs. `postrust-server` serves its WebSocket routes with
417/// its own handler, which settles the identity from `connection_init` exactly
418/// as an HTTP request's is settled; an embedder needing the same must insert a
419/// [`GraphQLContext`](crate::context::GraphQLContext) per operation itself.
420pub async fn graphql_ws_handler(
421    State(state): State<Arc<GraphQLState>>,
422    protocol: async_graphql_axum::GraphQLProtocol,
423    ws: axum::extract::WebSocketUpgrade,
424) -> impl IntoResponse {
425    let schema = state.schema.clone();
426    let pool = state.pool.clone();
427    let broker = Arc::clone(&state.broker);
428
429    ws.protocols(["graphql-transport-ws", "graphql-ws"])
430        .on_upgrade(move |socket| async move {
431            let mut data = async_graphql::Data::default();
432            data.insert(pool);
433            data.insert(broker);
434
435            async_graphql_axum::GraphQLWebSocket::new(socket, schema, protocol)
436                .with_data(data)
437                .serve()
438                .await
439        })
440}
441
442/// Handle GraphQL playground request.
443pub async fn graphql_playground() -> impl axum::response::IntoResponse {
444    axum::response::Html(async_graphql::http::playground_source(
445        async_graphql::http::GraphQLPlaygroundConfig::new("/graphql")
446            .subscription_endpoint("/graphql/ws"),
447    ))
448}
449
450/// The column a field name refers to.
451///
452/// The name itself, unless the table exposes that column under another one.
453/// Every path from a request to SQL goes through here: a renamed column is a
454/// name in the schema and in nothing else, so the translation has to happen at
455/// each boundary rather than once.
456fn column_for<'a>(
457    names: &'a crate::names::NameOverrides,
458    schema: &str,
459    table: &str,
460    field: &'a str,
461) -> &'a str {
462    names.column_source(schema, table, field).unwrap_or(field)
463}
464
465/// The same, given the table itself.
466fn table_column_for<'a>(
467    names: &'a crate::names::NameOverrides,
468    table: &postrust_core::schema_cache::Table,
469    field: &'a str,
470) -> &'a str {
471    column_for(names, &table.schema, &table.name, field)
472}
473
474/// The select list that renames a table's columns to the fields they are
475/// exposed as, or `None` where nothing is renamed.
476///
477/// Applied to a projection over the table rather than to the table itself, so
478/// the row a computed field or a computed relationship is passed stays the
479/// table's own composite -- `cannot cast type record to author` is what
480/// happens when it does not.
481fn rename_projection(
482    table: &postrust_core::schema_cache::Table,
483    alias: &str,
484    names: &crate::names::NameOverrides,
485) -> Option<String> {
486    if !names.renames_columns(&table.schema, &table.name) {
487        return None;
488    }
489    let parts: Vec<String> = table
490        .columns
491        .values()
492        .map(|column| {
493            let exposed = names
494                .column(&table.schema, &table.name, &column.name)
495                .unwrap_or(&column.name);
496            format!(
497                "{}.{} AS {}",
498                postrust_sql::escape_ident(alias),
499                postrust_sql::escape_ident(&column.name),
500                postrust_sql::escape_ident(exposed)
501            )
502        })
503        .collect();
504    match parts.is_empty() {
505        true => None,
506        false => Some(parts.join(", ")),
507    }
508}
509
510/// A table's column types, keyed by the names those columns are exposed under.
511///
512/// [`row_json`] names the shape columns it has to rewrite as GeoJSON, and it
513/// names them in whatever the projection called them. Over a renamed
514/// projection that is the field name, not the column's.
515fn exposed_column_types(
516    table: &postrust_core::schema_cache::Table,
517    names: &crate::names::NameOverrides,
518) -> HashMap<String, String> {
519    table
520        .columns
521        .values()
522        .map(|column| {
523            let exposed = names
524                .column(&table.schema, &table.name, &column.name)
525                .unwrap_or(&column.name);
526            (exposed.to_string(), column.nominal_type.clone())
527        })
528        .collect()
529}
530
531/// The type a value is written as, which keeps the list an array column is.
532///
533/// Not the leaf scalar: `c34_text_array` is a `text[]`, and a client sending
534/// `$textArray: [String]` into it was refused for offering a list where the
535/// input said `String`. Every write is optional, so nothing here is non-null.
536fn write_type_ref(graphql_type: &crate::types::GraphQLType) -> TypeRef {
537    match graphql_type {
538        crate::types::GraphQLType::List(inner) => TypeRef::named_list(leaf_scalar_name(inner)),
539        other => TypeRef::named(leaf_scalar_name(other)),
540    }
541}
542
543/// The scalar at the bottom of a type, past any list wrapping.
544fn leaf_scalar_name(graphql_type: &crate::types::GraphQLType) -> String {
545    match graphql_type {
546        crate::types::GraphQLType::List(inner) => leaf_scalar_name(inner),
547        other => other.to_string(),
548    }
549}
550
551/// Build the dynamic async-graphql schema from our generated schema.
552fn build_dynamic_schema(
553    generated: &GeneratedSchema,
554    _schema_cache: &SchemaCache,
555    subscription_fields: Option<&[SubField]>,
556    max_rows: Option<i64>,
557    names: Arc<crate::names::NameOverrides>,
558    subscription_refresh: std::time::Duration,
559    // Whose schema this is, where it is one role's. What it decides here is
560    // which columns a write may name, which is not the same question as which
561    // columns may be read -- Hasura grants the two separately, and a role that
562    // may set a column it cannot read is ordinary rather than exotic.
563    role: Option<&str>,
564) -> Result<Schema, GraphQLError> {
565    // Create object types for each table
566    let mut object_types: HashMap<String, Object> = HashMap::new();
567
568    // A table with no readable field gets no type at all. That is not an
569    // empty table -- it is one this role may write and not read, and a GraphQL
570    // object type with no fields is not a legal type. Everything that would
571    // have returned it is left out with it.
572    for (type_name, obj) in &generated.object_types {
573        if obj.fields.is_empty() {
574            continue;
575        }
576        let relationships = generated
577            .relationship_fields
578            .get(type_name)
579            .map(|r| r.as_slice())
580            .unwrap_or(&[]);
581        let table_obj = create_object_type(
582            obj,
583            relationships,
584            generated.federation_entities.get(type_name),
585        );
586        object_types.insert(type_name.clone(), table_obj);
587    }
588
589    // Aggregate types: every readable table gets them, because every table has
590    // a count even when it has nothing to sum -- and so does a table a role may
591    // count and not read, which is the whole of what such a role was granted.
592    // Read from the roots that were generated rather than worked out again,
593    // for the reason the write inputs are: two answers to one question drift.
594    let countable: HashSet<&str> = generated
595        .query_fields
596        .iter()
597        .filter(|field| field.aggregates && !field.is_by_pk)
598        .map(|field| field.type_name.as_str())
599        .collect();
600    for (type_name, obj) in &generated.object_types {
601        if obj.fields.is_empty() && !countable.contains(type_name.as_str()) {
602            continue;
603        }
604        for aggregate_type in create_aggregate_types(&obj.local_name, obj) {
605            object_types.insert(aggregate_type.type_name().to_string(), aggregate_type);
606        }
607    }
608
609    // One mutation response per table that has any mutation, and only those:
610    // an unreferenced type is still a type, and a read-only view would
611    // otherwise contribute a response nothing can return.
612    let mutable: HashMap<String, String> = generated
613        .mutation_fields
614        .iter()
615        .filter_map(|f| {
616            // The table's own name. A bulk write already answers with the
617            // response type, and trimming the suffix here is what keeps
618            // `<t>_mutation_response_mutation_response` from being built: the
619            // set used to be carried by `insert_one` and `update_by_pk`
620            // naming the bare type, so a table with only bulk writes -- which
621            // is what a role that cannot read one has -- registered the wrong
622            // name and left the right one missing.
623            let response = f
624                .return_type
625                .trim_matches(|c| c == '[' || c == ']' || c == '!')
626                .strip_suffix("_mutation_response")?;
627            Some((response.to_string(), f.type_name.clone()))
628        })
629        .collect();
630    for (base_name, row_type_name) in mutable {
631        // Whether it has rows to give back. A write to a table this role
632        // cannot read answers with `affected_rows` and nothing else -- there
633        // is no row type for `returning` to be a list of.
634        let returning = generated
635            .object_types
636            .get(&row_type_name)
637            .is_some_and(|object| !object.fields.is_empty());
638        object_types.insert(
639            mutation_response_type_name(&base_name),
640            create_mutation_response_type(&base_name, &row_type_name, returning),
641        );
642    }
643
644    // Non-federated Hasura-compatible schemas use `query_root`, not `Query`.
645    // Federation SDL from async-graphql does not include an operation-root
646    // schema definition, so Apollo composition treats only the conventional
647    // root names as root types.
648    let query_root = if generated.enable_federation {
649        "Query"
650    } else {
651        "query_root"
652    };
653    let mutation_root = if generated.enable_federation {
654        "Mutation"
655    } else {
656        "mutation_root"
657    };
658    let subscription_root = if generated.enable_federation {
659        "Subscription"
660    } else {
661        "subscription_root"
662    };
663
664    // Create query type. Resolvers need the relationship map to embed related
665    // rows, so it is shared into each closure.
666    let relationships = Arc::new(generated.relationship_fields.clone());
667    // (schema, table) -> GraphQL type name. A nested insert is handed a table
668    // by a relationship and has to find that table's relationships in turn,
669    // and the map they live in is keyed by type name -- which is not always
670    // the table's name, since a second schema prefixes it and a name may have
671    // been given.
672    let type_names: Arc<HashMap<(String, String), String>> = Arc::new(
673        generated
674            .object_types
675            .iter()
676            .map(|(type_name, object)| {
677                (
678                    (object.table.schema.clone(), object.table.name.clone()),
679                    type_name.clone(),
680                )
681            })
682            .collect(),
683    );
684    let query = add_function_fields(
685        create_query_type(
686            generated,
687            query_root,
688            max_rows,
689            Arc::clone(&relationships),
690            Arc::clone(&names),
691        ),
692        generated,
693        false,
694        max_rows,
695        Arc::clone(&relationships),
696        Arc::clone(&names),
697    );
698
699    // Create mutation type
700    let mutation = if !generated.mutation_fields.is_empty()
701        || generated.function_fields.iter().any(|f| f.volatile)
702    {
703        Some(add_function_fields(
704            create_mutation_type(
705                generated,
706                mutation_root,
707                Arc::clone(&relationships),
708                Arc::clone(&type_names),
709                Arc::clone(&names),
710                max_rows,
711                role,
712            ),
713            generated,
714            true,
715            max_rows,
716            Arc::clone(&relationships),
717            Arc::clone(&names),
718        ))
719    } else {
720        None
721    };
722
723    // Create subscription type if enabled
724    let subscription = subscription_fields.map(|_| {
725        create_subscription_type(
726            generated,
727            subscription_root,
728            max_rows,
729            Arc::clone(&relationships),
730            Arc::clone(&names),
731            subscription_refresh,
732        )
733    });
734
735    // Build schema
736    let mut builder = Schema::build(
737        query_root,
738        mutation.as_ref().map(|_| mutation_root),
739        subscription.as_ref().map(|_| subscription_root),
740    );
741    if generated.enable_federation {
742        builder = builder
743            .enable_federation()
744            .extension(NullableFederationEntity);
745        let entity_state = Arc::new(EntityResolverState::new(
746            generated,
747            Arc::clone(&relationships),
748            Arc::clone(&names),
749            max_rows,
750        ));
751        builder = builder.entity_resolver(move |ctx| {
752            let state = Arc::clone(&entity_state);
753            FieldFuture::new(async move { resolve_entities(&ctx, state.as_ref()).await })
754        });
755    }
756
757    // Register all object types
758    for (_, obj) in object_types {
759        builder = builder.register(obj);
760    }
761
762    // Register query type
763    builder = builder.register(query);
764
765    // Register mutation type if present
766    if let Some(mutation) = mutation {
767        builder = builder.register(mutation);
768    }
769
770    // Register subscription type if present
771    if let Some(subscription) = subscription {
772        builder = builder.register(subscription);
773    }
774
775    // Register scalar types
776    // Every scalar the generated types actually name, rather than a fixed
777    // list. A `geometry` column, a `raster` column and a database enum each
778    // become a scalar under their own PostgreSQL name, because that is the
779    // name a client's query declares its variables with -- and a scalar the
780    // schema mentions but never registers makes the whole schema unbuildable.
781    let (bool_exp_inputs, bool_exp_scalars) = crate::input::bool_exp::build_inputs(
782        &generated.object_types,
783        &generated.relationship_fields,
784    );
785    for input in bool_exp_inputs {
786        builder = builder.register(input);
787    }
788
789    let mut scalar_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
790    for object in generated.object_types.values() {
791        // Both halves. A column a role may set without seeing is named by the
792        // insert input and by nothing a read produces, so collecting only the
793        // readable fields leaves its scalar unregistered -- and a scalar the
794        // schema mentions but never registers makes the whole schema
795        // unbuildable, which costs the role every field it has.
796        for field in object.fields.iter().chain(&object.writable_fields) {
797            scalar_names.insert(leaf_scalar_name(&field.graphql_type));
798        }
799    }
800    // Used as an argument type in its own right, whether or not any column is
801    // one: `objects`, `_set` and the mutation inputs are still JSON.
802    scalar_names.insert("JSON".to_string());
803    // A function's arguments name scalars of their own: a table may have no
804    // geometry column while a function takes one.
805    for function in &generated.function_fields {
806        for (_, pg_type, _) in &function.arguments {
807            scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
808        }
809    }
810    // The same for the functions behind computed fields, whose arguments are
811    // written where the field is asked for rather than at a root.
812    for object in generated.object_types.values() {
813        for field in &object.fields {
814            for (_, pg_type, _) in &field.arguments {
815                scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
816            }
817        }
818    }
819    for relationships in generated.relationship_fields.values() {
820        for relationship in relationships {
821            for (_, pg_type, _) in &relationship.arguments {
822                scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
823            }
824        }
825    }
826
827    // Every scalar the boolean expressions name, which is more than the
828    // scalars the columns are: a cast from a geometry names `geography`, and a
829    // raster comparison names `geometry`. Taken from what was generated rather
830    // than listed here, because a list here is a second place to keep in step
831    // and it fell out of step twice.
832    scalar_names.extend(bool_exp_scalars);
833
834    for name in scalar_names {
835        if matches!(name.as_str(), "Int" | "Float" | "String" | "Boolean" | "ID") {
836            continue;
837        }
838        // An enum table's type is an enum, not a scalar. It reaches this list
839        // because a column typed as one names it, and registering both would
840        // define the same type twice.
841        if generated.enum_types.contains_key(&name) {
842            continue;
843        }
844        builder = builder
845            .register(Scalar::new(&name).description(format!("The PostgreSQL `{}` type.", name)));
846    }
847
848    // Register the boolean expression inputs -- one per table, plus one
849    // comparison input per scalar the tables use.
850
851    // A key as one object, for `update_x_by_pk(pk_columns: {...})`. Only the
852    // by-key update spells its key this way; the by-key query and delete take
853    // the columns as arguments, and both spellings are what a generated client
854    // already sends.
855    let mut key_inputs: HashMap<String, InputObject> = HashMap::new();
856    for field in &generated.mutation_fields {
857        if field.mutation_type != MutationType::UpdateByPk || field.pk_columns.is_empty() {
858            continue;
859        }
860        let base_name = field.local_type_name.as_str();
861        let type_name = format!("{}_pk_columns_input", base_name);
862        let mut input = InputObject::new(&type_name)
863            .description(format!("The primary key of one {} row.", base_name));
864        for (column, pg_type) in &field.pk_columns {
865            input = input.field(InputValue::new(
866                names
867                    .column(&field.schema_name, &field.table_name, column)
868                    .unwrap_or(column),
869                TypeRef::named_nn(exposed_pk_argument_type(
870                    names.as_ref(),
871                    &field.schema_name,
872                    &field.table_name,
873                    column,
874                    pg_type,
875                )),
876            ));
877        }
878        key_inputs.insert(type_name, input);
879    }
880    for (_, input) in key_inputs {
881        builder = builder.register(input);
882    }
883
884    // What a mutation writes. `objects` and `_set` were `JSON`, which accepts
885    // anything and lets a client generate nothing: no completion, no codegen,
886    // and a misspelled column reaching the database instead of being caught
887    // before the request was sent. The same argument that made `where` a real
888    // type applies unchanged.
889    // Which types have an `_insert_input` at all, which is what a nested write
890    // names. Collected first because a relationship may point at a table this
891    // loop has not reached yet -- and because the answer is now a permission
892    // question: a role granted `insert` on `article` and only `select` on
893    // `author` gets an `article_insert_input`, and writing `author` inside it
894    // would name a type nobody registered. async-graphql refuses to build a
895    // schema with a dangling reference, so the whole role would have no schema
896    // over one field it was never allowed to use.
897    // Which types actually got each write input, recorded as they are built
898    // rather than predicted from the object type's fields. The two answers
899    // used to be the same and are not any more: an input is built from the
900    // write permission's columns, so a table whose readable columns include a
901    // number may still have no `_inc_input` -- and an argument naming a type
902    // nobody registered is a schema async-graphql refuses to build, which
903    // costs the role every field it has rather than the one it cannot use.
904    let insertable_types: HashSet<&str> = generated
905        .object_types
906        .iter()
907        .filter(|(_, object)| crate::input::mutation::is_insertable(&object.table))
908        .map(|(type_name, _)| type_name.as_str())
909        .collect();
910
911    for (type_name, object) in &generated.object_types {
912        let local_type_name = object.local_name.as_str();
913        let table = &object.table;
914        // Which columns a write may name, which is the write permission's
915        // answer and not the read permission's. A role granted `columns:
916        // ["age"]` on insert may not name `is_user` even where it can read it,
917        // and the corpus tests exactly that: `field 'is_user' not found in
918        // type: 'resident_insert_input'`.
919        // Only real columns are written. A computed field is a function of the
920        // row and a relationship is handled separately below.
921        let writable_named = |allowed: Option<&crate::names::ColumnSet>| {
922            object
923                .writable_fields
924                .iter()
925                .filter(|field| {
926                    let column = table_column_for(&names, table, &field.name);
927                    table.get_column(column).is_some()
928                        && allowed.is_none_or(|set| set.allows(column))
929                })
930                .collect::<Vec<&crate::schema::object::GraphQLField>>()
931        };
932        let writable = writable_named(None);
933        let insert_fields = write_fields(object, &names, role, crate::role::Verb::Insert);
934        let update_fields = write_fields(object, &names, role, crate::role::Verb::Update);
935        if writable.is_empty() {
936            continue;
937        }
938
939        let relationships = generated
940            .relationship_fields
941            .get(type_name)
942            .map(|r| r.as_slice())
943            .unwrap_or(&[]);
944        let conflict_type = match table.unique_constraints.is_empty() {
945            true => None,
946            false => Some(format!("{}_on_conflict", local_type_name)),
947        };
948
949        if crate::input::mutation::is_insertable(table) {
950            let mut insert = InputObject::new(format!("{}_insert_input", local_type_name))
951                .description(format!("The columns of a new {} row.", local_type_name));
952            let mut taken: HashSet<&str> = HashSet::new();
953            for field in &insert_fields {
954                // Every column optional: which ones the database insists on is
955                // the database's answer, and a column that is NOT NULL with a
956                // default does not have to be given.
957                taken.insert(field.name.as_str());
958                insert = insert.field(InputValue::new(
959                    &field.name,
960                    write_type_ref(&field.graphql_type),
961                ));
962            }
963            // A nested write: the rows to insert beside this one. Only where
964            // the far side is something this schema can write -- see
965            // `insertable_types`.
966            for relationship in relationships {
967                if !insertable_types.contains(relationship.target_type.as_str()) {
968                    continue;
969                }
970                if !taken.insert(relationship.name.as_str()) {
971                    continue;
972                }
973                let suffix = match relationship.is_list {
974                    true => "arr_rel_insert_input",
975                    false => "obj_rel_insert_input",
976                };
977                insert = insert.field(InputValue::new(
978                    &relationship.name,
979                    TypeRef::named(format!("{}_{}", relationship.target_local_name, suffix)),
980                ));
981            }
982            builder = builder.register(insert);
983
984            // How this table is written as somebody else's nested row. Both
985            // shapes carry an `on_conflict`, which is what makes a nested
986            // upsert expressible.
987            let data_type = format!("{}_insert_input", local_type_name);
988            let mut object_rel =
989                InputObject::new(format!("{}_obj_rel_insert_input", local_type_name))
990                    .description(format!(
991                        "One {} row written beside its parent.",
992                        local_type_name
993                    ))
994                    .field(InputValue::new("data", TypeRef::named_nn(&data_type)));
995            let mut array_rel =
996                InputObject::new(format!("{}_arr_rel_insert_input", local_type_name))
997                    .description(format!(
998                        "{} rows written beside their parent.",
999                        local_type_name
1000                    ))
1001                    .field(InputValue::new(
1002                        "data",
1003                        TypeRef::named_nn_list_nn(&data_type),
1004                    ));
1005            if let Some(conflict) = &conflict_type {
1006                object_rel =
1007                    object_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1008                array_rel =
1009                    array_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1010            }
1011            builder = builder.register(object_rel).register(array_rel);
1012        }
1013
1014        if crate::input::mutation::is_updatable(table) {
1015            let mut set = InputObject::new(format!("{}_set_input", local_type_name))
1016                .description(format!("Columns of {} to replace.", local_type_name));
1017            let mut numeric = InputObject::new(format!("{}_inc_input", local_type_name))
1018                .description(format!("Columns of {} to add to.", local_type_name));
1019            let mut any_numeric = false;
1020            let mut any_jsonb = false;
1021            for field in &update_fields {
1022                if matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1023                    any_jsonb = true;
1024                }
1025                set = set.field(InputValue::new(
1026                    &field.name,
1027                    write_type_ref(&field.graphql_type),
1028                ));
1029                // Only a number can be added to, and a list of them is not a
1030                // number: `_inc` takes the scalar or nothing.
1031                if crate::schema::aggregate::is_incrementable(&field.graphql_type) {
1032                    any_numeric = true;
1033                    numeric = numeric.field(InputValue::new(
1034                        &field.name,
1035                        TypeRef::named(leaf_scalar_name(&field.graphql_type)),
1036                    ));
1037                }
1038            }
1039            builder = builder.register(set);
1040            // A table with nothing to add to gets no type for adding to it.
1041            if any_numeric {
1042                builder = builder.register(numeric);
1043            }
1044
1045            // One entry of `update_x_many`: a filter and the values to write
1046            // where it matches. The whole list runs in one transaction, in the
1047            // order it was given, which is what makes it different from
1048            // sending the updates one at a time.
1049            let mut updates = InputObject::new(format!("{}_updates", local_type_name))
1050                .description(format!(
1051                    "One update to {}: which rows, and what to write.",
1052                    local_type_name
1053                ))
1054                .field(InputValue::new(
1055                    "where",
1056                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1057                ))
1058                .field(InputValue::new(
1059                    "_set",
1060                    TypeRef::named(format!("{}_set_input", local_type_name)),
1061                ));
1062            if any_numeric {
1063                updates = updates.field(InputValue::new(
1064                    "_inc",
1065                    TypeRef::named(format!("{}_inc_input", local_type_name)),
1066                ));
1067            }
1068            // What a document column may be told to do, one input per
1069            // operator: each takes a value of a different shape -- a
1070            // document, a key, an index, a path -- and only the columns that
1071            // hold documents may be told any of it. A table with none is not
1072            // given the operators at all, which is what says in the schema
1073            // that `_append` is not a thing to write there.
1074            for (operator, _) in JSONB_OPERATORS {
1075                if !any_jsonb {
1076                    break;
1077                }
1078                updates = updates.field(InputValue::new(
1079                    *operator,
1080                    TypeRef::named(jsonb_operator_input(local_type_name, operator)),
1081                ));
1082            }
1083            builder = builder.register(updates);
1084            if any_jsonb {
1085                for (operator, item) in JSONB_OPERATORS {
1086                    let mut input =
1087                        InputObject::new(jsonb_operator_input(local_type_name, operator))
1088                            .description(format!(
1089                                "Columns of {} to apply `{}` to.",
1090                                local_type_name, operator
1091                            ));
1092                    for field in &update_fields {
1093                        if !matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1094                            continue;
1095                        }
1096                        input = input.field(InputValue::new(&field.name, item()));
1097                    }
1098                    builder = builder.register(input);
1099                }
1100            }
1101        }
1102    }
1103
1104    // A function's own arguments, under a name of their own so they cannot
1105    // collide with `where` or `limit`.
1106    for function in &generated.function_fields {
1107        if function.arguments.is_empty() {
1108            continue;
1109        }
1110        let mut args = InputObject::new(format!("{}_args", function.name))
1111            .description(format!("Arguments to {}.", function.name));
1112        for (name, pg_type, _) in &function.arguments {
1113            let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1114            // Nullable, whether or not the parameter has a default. Every
1115            // PostgreSQL argument accepts a null, and a client passing one
1116            // through a variable declares it as the nullable type -- `query
1117            // ($point: json)` used where `json!` was expected is a query the
1118            // spec's variable rule refuses. Whether a defaulted argument may
1119            // be left out entirely is enforced where the call is written,
1120            // which is the only place that can tell.
1121            args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1122        }
1123        builder = builder.register(args);
1124    }
1125
1126    // The same for a computed relationship's function, which takes its
1127    // arguments where it is embedded rather than at the root. Named after the
1128    // type and the field, since two tables may reach the same function and one
1129    // table may reach two.
1130    for (type_name, relationships) in &generated.relationship_fields {
1131        let local_type_name = generated
1132            .object_types
1133            .get(type_name)
1134            .map(|object| object.local_name.as_str())
1135            .unwrap_or(type_name);
1136        for relationship in relationships {
1137            if relationship.arguments.is_empty() {
1138                continue;
1139            }
1140            let mut args =
1141                InputObject::new(computed_args_type_name(local_type_name, &relationship.name))
1142                    .description(format!(
1143                        "Arguments to {}, beside the row it is asked of.",
1144                        relationship.name
1145                    ));
1146            for (name, pg_type, _) in &relationship.arguments {
1147                let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1148                // Nullable, for the reason the function arguments above are.
1149                args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1150            }
1151            builder = builder.register(args);
1152        }
1153    }
1154
1155    // And for a computed *column*'s function, which likewise takes its
1156    // arguments where the field is asked for. `locations { distance(args: {
1157    // from: ... }) }` -- the field is a function of the row and of what the
1158    // caller wants measured against it.
1159    for object in generated.object_types.values() {
1160        let local_type_name = object.local_name.as_str();
1161        for field in &object.fields {
1162            if field.arguments.is_empty() {
1163                continue;
1164            }
1165            let mut args = InputObject::new(computed_args_type_name(local_type_name, &field.name))
1166                .description(format!(
1167                    "Arguments to {}, beside the row it is asked of.",
1168                    field.name
1169                ));
1170            for (name, pg_type, _) in &field.arguments {
1171                let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1172                // Nullable, for the reason the function arguments above are.
1173                args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1174            }
1175            builder = builder.register(args);
1176        }
1177    }
1178
1179    // What a predicate over a related set may ask. Registered only for the
1180    // tables something points at with a to-many relationship, which is the
1181    // only place the field naming them exists.
1182    {
1183        use crate::input::bool_exp as be;
1184        let mut targets: BTreeMap<&str, &str> = BTreeMap::new();
1185        for relationships in generated.relationship_fields.values() {
1186            for relationship in relationships {
1187                if relationship.is_list {
1188                    targets.insert(
1189                        relationship.target_local_name.as_str(),
1190                        relationship.target_type.as_str(),
1191                    );
1192                }
1193            }
1194        }
1195        for (target, target_type) in targets {
1196            let Some(object) = generated.object_types.get(target_type) else {
1197                continue;
1198            };
1199            // A boolean column is what `bool_and` and `bool_or` fold, and a
1200            // GraphQL enum may not be empty -- so a table with none gets
1201            // neither the aggregates nor the enum naming their columns.
1202            let booleans: Vec<&str> = object
1203                .fields
1204                .iter()
1205                .filter(|field| {
1206                    object.table.get_column(&field.name).is_some()
1207                        && matches!(field.graphql_type, crate::types::GraphQLType::Boolean)
1208                })
1209                .map(|field| field.name.as_str())
1210                .collect();
1211
1212            let mut over = InputObject::new(be::aggregate_bool_exp_type_name(target)).description(
1213                format!("A question about the whole set of related {} rows.", target),
1214            );
1215            for function in be::AGGREGATE_PREDICATES {
1216                if function != "count" && booleans.is_empty() {
1217                    continue;
1218                }
1219                over = over.field(InputValue::new(
1220                    function,
1221                    TypeRef::named(be::aggregate_bool_exp_function(target, function)),
1222                ));
1223
1224                // `count` may be told which columns to count, as the field
1225                // itself may; `bool_and` folds exactly one.
1226                let (arguments, predicate) = match function {
1227                    "count" => (
1228                        TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1229                            target,
1230                        )),
1231                        be::comparison_type_name("Int"),
1232                    ),
1233                    _ => (
1234                        TypeRef::named_nn(be::aggregate_bool_exp_columns(target, function)),
1235                        be::comparison_type_name("Boolean"),
1236                    ),
1237                };
1238                let mut input = InputObject::new(be::aggregate_bool_exp_function(target, function))
1239                    .description(format!(
1240                        "`{}` over the related {} rows, and what it has to be.",
1241                        function, target
1242                    ))
1243                    .field(InputValue::new("arguments", arguments))
1244                    .field(InputValue::new("distinct", TypeRef::named("Boolean")))
1245                    .field(InputValue::new(
1246                        "filter",
1247                        TypeRef::named(be::bool_exp_type_name(target)),
1248                    ))
1249                    .field(InputValue::new("predicate", TypeRef::named_nn(predicate)));
1250                if function == "count" {
1251                    input = input.description(format!(
1252                        "How many related {} rows there have to be.",
1253                        target
1254                    ));
1255                }
1256                builder = builder.register(input);
1257
1258                if function != "count" {
1259                    let mut columns = Enum::new(be::aggregate_bool_exp_columns(target, function))
1260                        .description(format!("A boolean column of {}.", target));
1261                    for name in &booleans {
1262                        columns = columns.item(EnumItem::new(*name));
1263                    }
1264                    builder = builder.register(columns);
1265                }
1266            }
1267            builder = builder.register(over);
1268        }
1269    }
1270
1271    // Upserts. A table with no unique constraint has no conflict to resolve,
1272    // and a GraphQL enum may not be empty, so it gets none of these types
1273    // rather than an unusable set of them.
1274    for object in generated.object_types.values() {
1275        let local_type_name = object.local_name.as_str();
1276        // What an upsert may write, which is the update permission's answer
1277        // and not the read permission's: `update_columns` names columns to
1278        // overwrite, so a role that may set a column without seeing it may
1279        // name it here. Where no update permission narrows them these are the
1280        // table's own columns, which is what the enum listed before.
1281        let updatable_columns = write_fields(object, &names, role, crate::role::Verb::Update);
1282        if object.table.unique_constraints.is_empty() {
1283            continue;
1284        }
1285
1286        let mut constraints =
1287            Enum::new(format!("{}_constraint", local_type_name)).description(format!(
1288                "A uniqueness of {} that an insert may conflict with.",
1289                local_type_name
1290            ));
1291        for (name, columns) in &object.table.unique_constraints {
1292            constraints = constraints
1293                .item(EnumItem::new(name).description(format!("unique ({})", columns.join(", "))));
1294        }
1295
1296        let mut updatable = Enum::new(format!("{}_update_column", local_type_name)).description(
1297            format!("A column of {} an upsert may write.", local_type_name),
1298        );
1299        for field in &updatable_columns {
1300            updatable = updatable.item(EnumItem::new(&field.name));
1301        }
1302        // An update permission granting no column leaves the enum with no
1303        // members, which is not a legal type -- and dropping the enum would
1304        // drop `on_conflict` with it, so an insert that names one would fail
1305        // to build rather than be refused. Hasura's answer is a member that
1306        // names no column: the upsert is still expressible, `update_columns:
1307        // []` still means `DO NOTHING`, and naming the placeholder is refused
1308        // where it is used. `article_on_conflict_restricted_role.yaml` tests
1309        // both halves.
1310        if updatable_columns.is_empty() {
1311            updatable = updatable.item(
1312                EnumItem::new(PLACEHOLDER_COLUMN)
1313                    .description("No column may be written by this upsert."),
1314            );
1315        }
1316
1317        let on_conflict = InputObject::new(format!("{}_on_conflict", local_type_name))
1318            .description(format!(
1319                "What to do when an insert into {} conflicts.",
1320                local_type_name
1321            ))
1322            .field(InputValue::new(
1323                "constraint",
1324                TypeRef::named_nn(format!("{}_constraint", local_type_name)),
1325            ))
1326            // An empty list is `DO NOTHING`, which is how Hasura spells "leave
1327            // the row that is already there alone" -- and is the default, so
1328            // `on_conflict: {constraint: article_pkey}` means exactly that
1329            // rather than being refused for saying nothing about columns.
1330            .field(
1331                InputValue::new(
1332                    "update_columns",
1333                    TypeRef::named_nn_list_nn(format!("{}_update_column", local_type_name)),
1334                )
1335                .default_value(Value::List(Vec::new())),
1336            )
1337            .field(InputValue::new(
1338                "where",
1339                TypeRef::named(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1340            ));
1341
1342        builder = builder
1343            .register(constraints)
1344            .register(updatable)
1345            .register(on_conflict);
1346    }
1347
1348    // The enums generated from enum tables.
1349    for (type_name, members) in &generated.enum_types {
1350        let mut generated_enum =
1351            Enum::new(type_name).description(format!("The values {} allows.", type_name));
1352        for (value, comment) in members {
1353            let item = EnumItem::new(value);
1354            generated_enum = generated_enum.item(match comment {
1355                Some(description) => item.description(description),
1356                None => item,
1357            });
1358        }
1359        builder = builder.register(generated_enum);
1360    }
1361
1362    // Ordering: one input per table, one column enum per table, and the single
1363    // direction enum they all share.
1364    let (order_inputs, order_enums) = crate::input::order_by::build_inputs(
1365        &generated.object_types,
1366        &generated.relationship_fields,
1367    );
1368    for input in order_inputs {
1369        builder = builder.register(input);
1370    }
1371    for enum_type in order_enums {
1372        builder = builder.register(enum_type);
1373    }
1374
1375    builder
1376        .finish()
1377        .map_err(|e| GraphQLError::SchemaError(e.to_string()))
1378}
1379
1380/// Create an object type from a TableObjectType.
1381/// The enum member that stands in for an update permission granting no column.
1382///
1383/// Hasura's spelling, and it is load-bearing: `article_update_column` must be
1384/// a legal enum for `article_on_conflict` to exist, and `on_conflict` must
1385/// exist for an insert that carries one to be refused rather than unbuildable.
1386const PLACEHOLDER_COLUMN: &str = "_PLACEHOLDER";
1387
1388/// The name of a table's mutation response type.
1389pub fn mutation_response_type_name(base_name: &str) -> String {
1390    format!("{}_mutation_response", base_name)
1391}
1392
1393/// Build the `<table>_mutation_response` object.
1394///
1395/// Every bulk mutation answers with this rather than with the rows directly.
1396/// `affected_rows` is the count PostgreSQL reports, which is not the length of
1397/// `returning`: a client may ask for no rows back at all and still need to know
1398/// how many were touched, and that is the usual case for a delete.
1399fn create_mutation_response_type(base_name: &str, row_type: &str, returning: bool) -> Object {
1400    let response_name = mutation_response_type_name(base_name);
1401
1402    let response = Object::new(&response_name)
1403        .description(format!("The rows {} changed, and how many.", base_name))
1404        .field(Field::new(
1405            "affected_rows",
1406            TypeRef::named_nn(TypeRef::INT),
1407            |ctx| {
1408                FieldFuture::new(async move {
1409                    if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1410                        if let Some(value) = map.get(&async_graphql::Name::new("affected_rows")) {
1411                            return Ok(Some(FieldValue::value(value.clone())));
1412                        }
1413                    }
1414                    Ok(Some(FieldValue::value(Value::from(0))))
1415                })
1416            },
1417        ));
1418    // A table this role may write and not read has no row type, so there is
1419    // nothing for `returning` to be a list of. Hasura answers such a write
1420    // with the count alone, and the corpus asks for nothing else.
1421    if !returning {
1422        return response;
1423    }
1424    response.field(Field::new(
1425        "returning",
1426        TypeRef::named_nn_list_nn(row_type.to_string()),
1427        |ctx| {
1428            FieldFuture::new(async move {
1429                let rows = match ctx.parent_value.as_value() {
1430                    Some(Value::Object(map)) => {
1431                        match map.get(&async_graphql::Name::new("returning")) {
1432                            Some(Value::List(items)) => items.clone(),
1433                            _ => Vec::new(),
1434                        }
1435                    }
1436                    _ => Vec::new(),
1437                };
1438                Ok(Some(FieldValue::list(
1439                    rows.into_iter().map(FieldValue::value),
1440                )))
1441            })
1442        },
1443    ))
1444}
1445
1446/// Build every aggregate type for one table.
1447///
1448/// Four kinds, and they nest: `<t>_aggregate` holds `aggregate` and `nodes`,
1449/// `<t>_aggregate_fields` holds `count` and one field per function, and each
1450/// function has a type of its own holding the columns it applies to.
1451fn create_aggregate_types(base_name: &str, object: &TableObjectType) -> Vec<Object> {
1452    use crate::schema::aggregate as agg;
1453
1454    let mut types = Vec::new();
1455
1456    // `<t>_aggregate`: the rows, and the numbers about them.
1457    let fields_type = agg::aggregate_fields_type_name(base_name);
1458    // Whether there are rows to hand back beside the numbers. A role granted
1459    // "how many" and not "which" has no row type, so `nodes` has nothing to be
1460    // a list of.
1461    let has_rows = !object.fields.is_empty();
1462    let mut over = Object::new(agg::aggregate_type_name(base_name))
1463        .description(match has_rows {
1464            true => format!("Aggregates over {}, with the rows themselves.", base_name),
1465            false => format!("Aggregates over {}.", base_name),
1466        })
1467        .field(Field::new(
1468            "aggregate",
1469            TypeRef::named(&fields_type),
1470            |ctx| FieldFuture::new(async move { Ok(child_of(&ctx, "aggregate")) }),
1471        ));
1472    if has_rows {
1473        over = over.field(Field::new(
1474            "nodes",
1475            TypeRef::named_nn_list_nn(object.name.clone()),
1476            |ctx| {
1477                FieldFuture::new(async move {
1478                    let rows = match child_value(&ctx, "nodes") {
1479                        Some(Value::List(items)) => items,
1480                        _ => Vec::new(),
1481                    };
1482                    Ok(Some(FieldValue::list(
1483                        rows.into_iter().map(FieldValue::value),
1484                    )))
1485                })
1486            },
1487        ));
1488    }
1489    types.push(over);
1490
1491    // `<t>_aggregate_fields`: count, and one field per function.
1492    let mut count = Field::new("count", TypeRef::named_nn(TypeRef::INT), |ctx| {
1493        // Read under the name it was asked for, not under `count`.
1494        // Two counts of different things sit in one selection --
1495        // `count` beside `distinct_authors: count(columns: [author_id],
1496        // distinct: true)` -- and they are different numbers.
1497        let key = ctx.ctx.field().alias().unwrap_or("count").to_string();
1498        FieldFuture::new(async move {
1499            Ok(Some(FieldValue::value(
1500                child_value(&ctx, &key).unwrap_or(Value::from(0)),
1501            )))
1502        })
1503    });
1504    // `count(columns:)` counts the rows where those columns are not null, and
1505    // `distinct` counts distinct values among them. Neither is offered where
1506    // the role may name no column: `count` is then a count of rows and nothing
1507    // else, and the corpus tests exactly that -- `'count' has no argument
1508    // named 'columns'`.
1509    if has_rows {
1510        count = count
1511            .argument(InputValue::new(
1512                "columns",
1513                TypeRef::named_nn_list(agg_select_column(base_name)),
1514            ))
1515            .argument(InputValue::new("distinct", TypeRef::named("Boolean")));
1516    }
1517    let mut aggregate_fields = Object::new(&fields_type)
1518        .description(format!("Aggregate functions over {}.", base_name))
1519        .field(count);
1520
1521    for (function, returns, columns) in agg::functions_for(object) {
1522        let function_type = agg::function_fields_type_name(base_name, function);
1523        let mut per_column = Object::new(&function_type).description(format!(
1524            "`{}` of each {} column it applies to.",
1525            function, base_name
1526        ));
1527        for column in &columns {
1528            let column_name = column.clone();
1529            let type_name = agg::field_type_for(object, column, returns);
1530            per_column =
1531                per_column.field(Field::new(column, TypeRef::named(type_name), move |ctx| {
1532                    let column_name = column_name.clone();
1533                    FieldFuture::new(async move {
1534                        Ok(child_value(&ctx, &column_name).map(FieldValue::value))
1535                    })
1536                }));
1537        }
1538        types.push(per_column);
1539
1540        let owned = function.to_string();
1541        aggregate_fields = aggregate_fields.field(Field::new(
1542            function,
1543            TypeRef::named(&function_type),
1544            move |ctx| {
1545                let owned = owned.clone();
1546                FieldFuture::new(async move { Ok(child_of(&ctx, &owned)) })
1547            },
1548        ));
1549    }
1550
1551    types.push(aggregate_fields);
1552    types
1553}
1554
1555/// The column enum a `count(columns:)` draws from.
1556fn agg_select_column(base_name: &str) -> String {
1557    crate::input::order_by::select_column_type_name(base_name)
1558}
1559
1560/// One key of the parent object, as a value.
1561fn child_value(ctx: &ResolverContext<'_>, key: &str) -> Option<Value> {
1562    match ctx.parent_value.as_value() {
1563        Some(Value::Object(map)) => map.get(&async_graphql::Name::new(key)).cloned(),
1564        _ => None,
1565    }
1566}
1567
1568/// One key of the parent object, as a resolvable child.
1569///
1570/// `None` where the key is absent or null, which is what a client that did not
1571/// ask for that aggregate should see.
1572fn child_of<'a>(ctx: &ResolverContext<'_>, key: &str) -> Option<FieldValue<'a>> {
1573    match child_value(ctx, key) {
1574        None | Some(Value::Null) => None,
1575        Some(value) => Some(FieldValue::value(value)),
1576    }
1577}
1578
1579/// An error carrying the code Hasura would give it.
1580///
1581/// The default coding reads the message, and a refusal written here has no
1582/// word in it that a message from PostgreSQL would not also have -- so it says
1583/// so itself rather than being guessed at.
1584fn coded_error(code: &'static str, message: impl Into<String>) -> async_graphql::Error {
1585    let mut error = async_graphql::Error::new(message);
1586    let mut extensions = async_graphql::ErrorExtensionValues::default();
1587    extensions.set("code", code);
1588    error.extensions = Some(extensions);
1589    error
1590}
1591
1592/// A refusal the client could have avoided by writing the request differently.
1593fn validation_error(message: impl Into<String>) -> async_graphql::Error {
1594    coded_error("validation-failed", message)
1595}
1596
1597/// A database error, classified the way Hasura classifies it.
1598///
1599/// PostgreSQL's own message is the whole of what either server says about
1600/// what went wrong; what Hasura adds is a word for *which kind* of wrong,
1601/// taken from the SQLSTATE rather than from the text: `Uniqueness violation.
1602/// duplicate key value violates unique constraint "author_name_key"`. A client
1603/// reporting that to a user is reporting text it already ships.
1604///
1605/// Read from the code, never from the message. The message is localised, the
1606/// code is not, and every attempt to tell these apart by reading English is
1607/// wrong the first time a server runs in another language.
1608fn database_error(error: sqlx::Error) -> async_graphql::Error {
1609    let sqlx::Error::Database(db) = &error else {
1610        return async_graphql::Error::new(error.to_string());
1611    };
1612    let described = match db.code().as_deref() {
1613        // Class 23, integrity constraint violation: the four PostgreSQL
1614        // raises and Hasura names.
1615        Some("23505") => "Uniqueness violation. ",
1616        Some("23502") => "Not-NULL violation. ",
1617        Some("23503") => "Foreign key violation. ",
1618        Some("23514") => "Check constraint violation. ",
1619        // Everything else is answered with PostgreSQL's own words and no
1620        // heading. Inventing one for a class Hasura says nothing about would
1621        // be a difference rather than a match.
1622        _ => "",
1623    };
1624    let error = async_graphql::Error::new(format!("{}{}", described, db.message()));
1625    // And the code, where the SQLSTATE says which one. Only the codes Hasura's
1626    // corpus pins are set: everything else is left for `code_for` to classify,
1627    // which is a guess from the message text and says so. Replacing that guess
1628    // wholesale would be the same guess with fewer places to notice it.
1629    let coded = match db.code().as_deref() {
1630        Some(code) if code.starts_with("23") => Some("constraint-violation"),
1631        // A `LIKE` pattern that ends mid-escape. Not a data exception to
1632        // Hasura -- the pattern came from the request, so the request is what
1633        // was wrong.
1634        Some("22025") => Some("bad-request"),
1635        // A negative `OFFSET`, and the rest of class 22 with it.
1636        Some(code) if code.starts_with("22") => Some("data-exception"),
1637        _ => None,
1638    };
1639    match coded {
1640        Some(code) => at_code(error, code),
1641        None => error,
1642    }
1643}
1644
1645/// The same error, coded as Hasura codes it.
1646fn at_code(mut error: async_graphql::Error, code: &'static str) -> async_graphql::Error {
1647    let mut extensions = error.extensions.take().unwrap_or_default();
1648    extensions.set("code", code);
1649    error.extensions = Some(extensions);
1650    error
1651}
1652
1653/// The same error, told where in the request it happened.
1654///
1655/// Hasura names the place a write went wrong inside the *arguments* --
1656/// `$.selectionSet.insert_author.args.objects[0].bio` -- and a response path
1657/// cannot say that: it names fields of the answer, and the answer has no
1658/// `objects`. So the path is written where the error is raised, by whoever
1659/// knows which row and which column it is about, and
1660/// [`crate::hasura::path_for`] prefers it over the response path.
1661fn at_path(mut error: async_graphql::Error, path: &str) -> async_graphql::Error {
1662    let mut extensions = error.extensions.take().unwrap_or_default();
1663    extensions.set("path", path);
1664    error.extensions = Some(extensions);
1665    error
1666}
1667
1668fn create_object_type(
1669    obj: &TableObjectType,
1670    relationships: &[RelationshipField],
1671    federation_entity: Option<&FederationEntity>,
1672) -> Object {
1673    let mut object = Object::new(&obj.name);
1674    let entity_is_shared = federation_entity.is_some_and(|entity| entity.shared);
1675    if let Some(entity) = federation_entity {
1676        object = object.key(entity.key_fields_sdl());
1677    }
1678
1679    // A table with no comment still gets a description, because Hasura gives
1680    // one and a client generating documentation from the schema would
1681    // otherwise show a blank where it used to show this.
1682    object = match obj.description() {
1683        // An empty description is one that was given and is empty: metadata
1684        // said the type has none, which is not the same as saying nothing.
1685        Some("") => object,
1686        Some(desc) => object.description(desc),
1687        None => object.description(format!(
1688            "columns and relationships of \"{}\"",
1689            obj.table_name()
1690        )),
1691    };
1692
1693    // A GraphQL object may not have two fields of one name, and a schema that
1694    // tries to build one aborts the process rather than returning an error.
1695    // Relationship names are derived from the table they point at, so a
1696    // foreign key column named after its own target -- `pizza.crust`
1697    // referencing `crust`, which is an ordinary way to write that schema --
1698    // produces exactly that clash.
1699    //
1700    // The column wins. It is the table's own data, it is what a client's
1701    // existing queries select, and dropping it to keep a derived name would
1702    // lose something the schema actually says. The relationship is left out
1703    // and said so, which is what Hasura does with the same clash: the
1704    // metadata is marked inconsistent and the field is simply not there.
1705    let mut taken: HashSet<String> = obj.fields.iter().map(|f| f.name.clone()).collect();
1706
1707    // Collected rather than added as they are built, so they can go on in name
1708    // order -- which is the order Hasura answers introspection in, and does not
1709    // depend on which column the catalogue happened to list first.
1710    let mut fields: Vec<(String, Field)> = Vec::new();
1711
1712    for field in &obj.fields {
1713        let field_name = field.name.clone();
1714        let field_type = graphql_type_ref(&field.type_string());
1715        // A document-valued column can be asked for one part of itself, the
1716        // same way `#>` reads one. It is answered here rather than in SQL
1717        // because the same column may be asked for under several aliases --
1718        // `c32_json(path: "a")` beside `c32_json(path: "arr[0]")` -- and
1719        // one projection cannot carry both.
1720        let takes_path = matches!(&field.graphql_type, crate::types::GraphQLType::Json)
1721            || matches!(
1722                &field.graphql_type,
1723                crate::types::GraphQLType::Custom(name) if name == "json"
1724            );
1725
1726        // Create field with resolver that extracts from parent async_graphql::Value
1727        // The query resolver stores rows as FieldValue::value(Value::Object)
1728        // so we use as_value() to get the Value and extract fields from the Object
1729        let gql_field = Field::new(&field.name, field_type, move |ctx| {
1730            let field_name = field_name.clone();
1731            FieldFuture::new(async move {
1732                // Get the parent value as async_graphql::Value using as_value()
1733                if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1734                    // Convert field name to async_graphql::Name for lookup
1735                    let key = async_graphql::Name::new(&field_name);
1736                    let path = match takes_path {
1737                        true => ctx.args.get("path").and_then(|v| v.string().ok()),
1738                        false => None,
1739                    };
1740                    match map.get(&key) {
1741                        // A null is the answer, not a value to resolve. An
1742                        // enum-typed column said so with `internal: invalid
1743                        // item for enum` rather than answering null, because a
1744                        // null is not one of its members.
1745                        Some(Value::Null) => return Ok(None),
1746                        Some(val) => {
1747                            let val = match path {
1748                                Some(path) => walk_json_path(val, path)?,
1749                                None => val.clone(),
1750                            };
1751                            return match val {
1752                                Value::Null => Ok(None),
1753                                val => Ok(Some(FieldValue::value(val))),
1754                            };
1755                        }
1756                        None => {}
1757                    }
1758                }
1759
1760                // Field not found or parent not a Value::Object
1761                Ok(None)
1762            })
1763        });
1764
1765        let gql_field = match takes_path {
1766            true => gql_field.argument(
1767                InputValue::new("path", TypeRef::named("String")).description("JSON select path"),
1768            ),
1769            false => gql_field,
1770        };
1771
1772        // A computed field whose function takes more than the row: the
1773        // caller writes those under `args`, for the reason a computed
1774        // relationship's are written there.
1775        let gql_field = match field.arguments.is_empty() {
1776            true => gql_field,
1777            false => gql_field.argument(InputValue::new(
1778                "args",
1779                TypeRef::named_nn(computed_args_type_name(&obj.local_name, &field.name)),
1780            )),
1781        };
1782
1783        let gql_field = if let Some(desc) = &field.description {
1784            gql_field.description(desc)
1785        } else {
1786            gql_field
1787        };
1788        let gql_field = if entity_is_shared && !field.is_pk {
1789            gql_field.shareable()
1790        } else {
1791            gql_field
1792        };
1793
1794        fields.push((field.name.clone(), gql_field));
1795    }
1796
1797    // Relationship fields. The query resolver embeds related rows into the
1798    // parent JSON before returning it, so these read from the parent value the
1799    // same way column fields do.
1800    for rel in relationships {
1801        if !taken.insert(rel.name.clone()) {
1802            tracing::warn!(
1803                "{}: not exposing the relationship to \"{}\" as `{}` -- the table \
1804                 already has a column of that name",
1805                obj.name,
1806                rel.target_type,
1807                rel.name
1808            );
1809            continue;
1810        }
1811        let field_name = rel.name.clone();
1812        let field_type = if rel.is_list {
1813            TypeRef::named_nn_list_nn(&rel.target_type)
1814        } else if always_present(rel, &obj.table) {
1815            TypeRef::named_nn(&rel.target_type)
1816        } else {
1817            TypeRef::named(&rel.target_type)
1818        };
1819
1820        let is_list = rel.is_list;
1821        let mut gql_field = Field::new(&rel.name, field_type, move |ctx| {
1822            let field_name = field_name.clone();
1823            FieldFuture::new(async move {
1824                if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1825                    let key = async_graphql::Name::new(&field_name);
1826                    match map.get(&key) {
1827                        // A row that points at nothing has no related row, and
1828                        // the answer to that is null -- not an object whose
1829                        // every field is null, which is what handing a null
1830                        // parent value to the field resolvers produced.
1831                        Some(Value::Null) if !is_list => return Ok(None),
1832                        Some(val) => return Ok(Some(FieldValue::value(val.clone()))),
1833                        None => {}
1834                    }
1835                }
1836                Ok(None)
1837            })
1838        });
1839
1840        // The same arguments the root field takes. An embedded list is a list
1841        // like any other: a client showing the five most recent articles of an
1842        // author has nowhere else to say so, and without these the only way to
1843        // narrow one is to fetch all of it and discard the rest in the client.
1844        // A to-one relationship is left alone -- there is nothing to order or
1845        // page through when the answer is one row.
1846        // A computed relationship's function may take more than the row --
1847        // "the articles of this author matching a search" -- and `args` is
1848        // where the caller writes them, so a term called `limit` cannot shadow
1849        // the one that pages the result.
1850        if !rel.arguments.is_empty() {
1851            gql_field = gql_field.argument(InputValue::new(
1852                "args",
1853                TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1854            ));
1855        }
1856        if rel.is_list {
1857            gql_field = gql_field
1858                .argument(InputValue::new(
1859                    "distinct_on",
1860                    TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1861                        &rel.target_local_name,
1862                    )),
1863                ))
1864                .argument(InputValue::new(
1865                    "where",
1866                    TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1867                        &rel.target_local_name,
1868                    )),
1869                ))
1870                .argument(InputValue::new(
1871                    "order_by",
1872                    TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1873                        &rel.target_local_name,
1874                    )),
1875                ))
1876                .argument(InputValue::new("limit", TypeRef::named("Int")))
1877                .argument(InputValue::new("offset", TypeRef::named("Int")));
1878        }
1879
1880        let gql_field = if let Some(desc) = &rel.description {
1881            gql_field.description(desc)
1882        } else {
1883            gql_field
1884        };
1885        let gql_field = if entity_is_shared {
1886            gql_field.shareable()
1887        } else {
1888            gql_field
1889        };
1890
1891        fields.push((rel.name.clone(), gql_field));
1892
1893        // `author { articles_aggregate { aggregate { count } } }` -- the count
1894        // of a row's children without fetching them, which is the query behind
1895        // every "12 comments" beside a post. Only for a relationship to many:
1896        // counting one row is not a question anyone asks.
1897        if rel.is_list {
1898            let aggregate_field = format!("{}_aggregate", rel.name);
1899            if taken.insert(aggregate_field.clone()) {
1900                let key = aggregate_field.clone();
1901                fields.push((
1902                    aggregate_field.clone(),
1903                    Field::new(
1904                        &aggregate_field,
1905                        TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
1906                            &rel.target_local_name,
1907                        )),
1908                        move |ctx| {
1909                            let key = key.clone();
1910                            FieldFuture::new(async move {
1911                                // Absent where the parent has no children at
1912                                // all; the aggregate type's own resolvers read
1913                                // a missing key as zero and an empty list.
1914                                Ok(Some(FieldValue::value(
1915                                    child_value(&ctx, &key).unwrap_or(Value::Null),
1916                                )))
1917                            })
1918                        },
1919                    )
1920                    // The same arguments the list itself takes. "How many of
1921                    // this author's articles were published this year" is the
1922                    // question a count is usually asked as, and without a
1923                    // `where` there is no way to write it.
1924                    .argument(InputValue::new(
1925                        "distinct_on",
1926                        TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1927                            &rel.target_local_name,
1928                        )),
1929                    ))
1930                    .argument(InputValue::new(
1931                        "where",
1932                        TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1933                            &rel.target_local_name,
1934                        )),
1935                    ))
1936                    .argument(InputValue::new(
1937                        "order_by",
1938                        TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1939                            &rel.target_local_name,
1940                        )),
1941                    ))
1942                    .argument(InputValue::new("limit", TypeRef::named("Int")))
1943                    .argument(InputValue::new("offset", TypeRef::named("Int")))
1944                    .description(format!("Aggregates over {}.", rel.name)),
1945                ));
1946                // Counting the rows a function answers with means calling it,
1947                // so the aggregate takes whatever the function takes.
1948                if !rel.arguments.is_empty() {
1949                    let last = fields.len() - 1;
1950                    let (name, field) = fields.remove(last);
1951                    fields.push((
1952                        name,
1953                        field.argument(InputValue::new(
1954                            "args",
1955                            TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1956                        )),
1957                    ));
1958                }
1959                if entity_is_shared {
1960                    let last = fields.len() - 1;
1961                    let (name, field) = fields.remove(last);
1962                    fields.push((name, field.shareable()));
1963                }
1964            }
1965        }
1966    }
1967
1968    fields.sort_by(|(a, _), (b, _)| a.cmp(b));
1969    for (_, field) in fields {
1970        object = object.field(field);
1971    }
1972
1973    object
1974}
1975
1976/// A root field, whichever root it is on.
1977///
1978/// A query field and a subscription field take the same arguments and mean the
1979/// same by them -- the difference is only whether the answer is sent once or
1980/// whenever it changes -- and the two builders share no trait of their own, so
1981/// this is what lets the argument list be written once.
1982trait RootField: Sized {
1983    fn with_argument(self, value: InputValue) -> Self;
1984}
1985
1986impl RootField for Field {
1987    fn with_argument(self, value: InputValue) -> Self {
1988        self.argument(value)
1989    }
1990}
1991
1992impl RootField for SubscriptionField {
1993    fn with_argument(self, value: InputValue) -> Self {
1994        self.argument(value)
1995    }
1996}
1997
1998/// The five arguments that narrow a set of rows, in the order Hasura lists
1999/// them.
2000fn with_row_arguments<F: RootField>(field: F, type_name: &str) -> F {
2001    with_row_arguments_named(field, type_name, true)
2002}
2003
2004/// The same, where `columns` says whether the table has any to name.
2005///
2006/// A table a role may count and not read has none: there is no column enum to
2007/// order by and none to be distinct on, so those two arguments are not there
2008/// -- and could not be, since the types they name are not built either. What
2009/// is left still narrows the set being counted, which is the whole of what
2010/// such a root is for.
2011fn with_row_arguments_named<F: RootField>(field: F, type_name: &str, columns: bool) -> F {
2012    let mut field = field;
2013    if columns {
2014        field = field.with_argument(InputValue::new(
2015            "distinct_on",
2016            TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(type_name)),
2017        ));
2018    }
2019    field = field
2020        .with_argument(InputValue::new("limit", TypeRef::named("Int")))
2021        .with_argument(InputValue::new("offset", TypeRef::named("Int")));
2022    if columns {
2023        field = field.with_argument(InputValue::new(
2024            "order_by",
2025            TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(type_name)),
2026        ));
2027    }
2028    field.with_argument(InputValue::new(
2029        "where",
2030        TypeRef::named(crate::input::bool_exp::bool_exp_type_name(type_name)),
2031    ))
2032}
2033
2034/// One argument per primary key column, named and typed after the column
2035/// itself rather than assuming an integer `id`.
2036fn with_key_arguments<F: RootField>(
2037    field: F,
2038    schema_name: &str,
2039    table_name: &str,
2040    pk_columns: &[(String, String)],
2041    names: &crate::names::NameOverrides,
2042) -> F {
2043    let mut field = field;
2044    for (column, pg_type) in pk_columns {
2045        field = field.with_argument(InputValue::new(
2046            names
2047                .column(schema_name, table_name, column)
2048                .unwrap_or(column),
2049            TypeRef::named_nn(exposed_pk_argument_type(
2050                names,
2051                schema_name,
2052                table_name,
2053                column,
2054                pg_type,
2055            )),
2056        ));
2057    }
2058    field
2059}
2060
2061/// Create the Query type with all table query fields.
2062fn create_query_type(
2063    generated: &GeneratedSchema,
2064    root_name: &str,
2065    max_rows: Option<i64>,
2066    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2067    names: Arc<crate::names::NameOverrides>,
2068) -> Object {
2069    // Collected rather than added as they are built, so they can go on in name
2070    // order: that is the order Hasura answers introspection in, and it is the
2071    // order a client diffing two schemas, or a generator writing its types
2072    // out, gets a stable answer from.
2073    let mut roots: Vec<(String, Field)> = Vec::new();
2074
2075    for field in &generated.query_fields {
2076        let table_name = field.table_name.clone();
2077        let schema_name = field.schema_name.clone();
2078        let type_name = field.type_name.clone();
2079        let local_type_name = field.local_type_name.clone();
2080        let is_by_pk = field.is_by_pk;
2081        let pk_columns = field.pk_columns.clone();
2082        let return_type = graphql_type_ref(&field.return_type);
2083
2084        let spec_type_name = local_type_name.clone();
2085        let spec = Arc::new(QueryFieldSpec {
2086            schema_name,
2087            table_name,
2088            type_name,
2089            is_by_pk,
2090            pk_columns: pk_columns.clone(),
2091            max_rows,
2092            relationships: Arc::clone(&relationships),
2093            names: Arc::clone(&names),
2094            call: None,
2095        });
2096
2097        let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2098            let spec = Arc::clone(&spec);
2099            FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
2100        });
2101
2102        // Add standard query arguments
2103        gql_field = match is_by_pk {
2104            false => with_row_arguments(gql_field, &spec_type_name),
2105            true => with_key_arguments(
2106                gql_field,
2107                &field.schema_name,
2108                &field.table_name,
2109                &pk_columns,
2110                names.as_ref(),
2111            ),
2112        };
2113
2114        if let Some(desc) = &field.description {
2115            gql_field = gql_field.description(desc);
2116        }
2117
2118        // A table this role may count and not read has no row type, so the
2119        // list root that would answer with one is not built. The entry is
2120        // still here for the aggregate root below, which is what such a role
2121        // was granted.
2122        if field.rows {
2123            roots.push((field.name.clone(), gql_field));
2124        }
2125
2126        // The same rows, with numbers about them. Same arguments as the list
2127        // field, because `author_aggregate(where: ...)` counts the set the
2128        // filter describes, not the whole table.
2129        //
2130        // Not built at all for a role whose permission withholds it: counting
2131        // rows is a way of learning about them, so it is granted separately
2132        // from reading them and refused by absence rather than at execution.
2133        if !is_by_pk && field.aggregates {
2134            let agg_spec = Arc::new(AggregateSpec {
2135                schema_name: field.schema_name.clone(),
2136                table_name: field.table_name.clone(),
2137                type_name: field.type_name.clone(),
2138                max_rows,
2139                relationships: Arc::clone(&relationships),
2140                names: Arc::clone(&names),
2141                call: None,
2142            });
2143            let aggregate_field_name = field.aggregate_name.clone().unwrap_or_else(|| {
2144                crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2145            });
2146            let aggregate_field_name_for_sorting = aggregate_field_name.clone();
2147            let mut agg_field = Field::new(
2148                aggregate_field_name,
2149                TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2150                    &field.local_type_name,
2151                )),
2152                move |ctx| {
2153                    let agg_spec = Arc::clone(&agg_spec);
2154                    FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
2155                },
2156            );
2157            agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2158            agg_field = match field.aggregate_description.as_deref() {
2159                Some("") => agg_field,
2160                Some(given) => agg_field.description(given),
2161                None => agg_field.description(format!(
2162                    "fetch aggregated fields from the table: \"{}\"",
2163                    field.table_name
2164                )),
2165            };
2166            roots.push((aggregate_field_name_for_sorting, agg_field));
2167        }
2168    }
2169
2170    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2171    let mut query = Object::new(root_name);
2172    // A GraphQL object may not have no fields, so a schema that exposes no
2173    // table needs something on its query root. Hasura puts a placeholder there
2174    // and calls it this; a client that reaches it has nothing to read.
2175    if roots.is_empty() {
2176        return query.field(
2177            Field::new("no_queries_available", TypeRef::named("String"), |_| {
2178                FieldFuture::new(async move { Ok(None::<Value>) })
2179            })
2180            .description("There are no queries available to the current role."),
2181        );
2182    }
2183    for (_, field) in roots {
2184        query = query.field(field);
2185    }
2186
2187    query
2188}
2189
2190/// One document operator: its name, and the type its columns are given.
2191type JsonbOperator = (&'static str, fn() -> TypeRef);
2192
2193/// What a document column may be told to do, and what each is told with.
2194///
2195/// `_append` and `_prepend` take another document; `_delete_key` takes a key;
2196/// `_delete_elem` takes an index into an array; `_delete_at_path` takes the
2197/// path to what is being removed. The shapes are per operator rather than per
2198/// column, which is why one input type per operator says all of it.
2199const JSONB_OPERATORS: &[JsonbOperator] = &[
2200    ("_append", || TypeRef::named("jsonb")),
2201    ("_delete_at_path", || TypeRef::named_nn_list("String")),
2202    ("_delete_elem", || TypeRef::named("Int")),
2203    ("_delete_key", || TypeRef::named("String")),
2204    ("_prepend", || TypeRef::named("jsonb")),
2205];
2206
2207/// The name of the input holding one document operator's columns.
2208fn jsonb_operator_input(type_name: &str, operator: &str) -> String {
2209    format!("{}{}_input", type_name, operator)
2210}
2211
2212/// Add the operators an update may be written with.
2213///
2214/// All optional, and at least one required -- which GraphQL cannot express, so
2215/// the resolver says so instead of the schema. Making `_set` non-null would
2216/// have been expressible and wrong: an update that only increments a counter
2217/// never sends one.
2218fn with_update_operators(
2219    field: Field,
2220    base_name: &str,
2221    has_numeric: bool,
2222    has_jsonb: bool,
2223) -> Field {
2224    let mut field = field;
2225    // A table with no document column is not offered the document operators:
2226    // there is nothing there to append to.
2227    if has_jsonb {
2228        for (operator, _) in JSONB_OPERATORS {
2229            field = field.argument(InputValue::new(
2230                *operator,
2231                TypeRef::named(jsonb_operator_input(base_name, operator)),
2232            ));
2233        }
2234    }
2235    if has_numeric {
2236        field = field.argument(InputValue::new(
2237            "_inc",
2238            TypeRef::named(format!("{}_inc_input", base_name)),
2239        ));
2240    }
2241    field.argument(InputValue::new(
2242        "_set",
2243        TypeRef::named(format!("{}_set_input", base_name)),
2244    ))
2245}
2246
2247/// Create the Mutation type with all mutation fields.
2248/// Which columns a write may name, and so which of its inputs exist.
2249///
2250/// One definition, called both where the inputs are registered and where the
2251/// arguments naming them are declared. They used to agree by coincidence --
2252/// both read the object type's fields -- and stopped the moment a write input
2253/// began following its own permission. An argument naming a type nobody
2254/// registered is a schema async-graphql refuses to build, which costs a role
2255/// every field it has over the one it cannot use.
2256fn write_fields<'a>(
2257    object: &'a crate::schema::object::TableObjectType,
2258    names: &crate::names::NameOverrides,
2259    role: Option<&str>,
2260    verb: crate::role::Verb,
2261) -> Vec<&'a crate::schema::object::GraphQLField> {
2262    let allowed = role.and_then(|role| {
2263        names
2264            .permissions(&object.table.schema, &object.table.name, role)
2265            .and_then(|granted| granted.write_columns(verb))
2266    });
2267    object
2268        .writable_fields
2269        .iter()
2270        .filter(|field| {
2271            let column = table_column_for(names, &object.table, &field.name);
2272            // A column PostgreSQL generates always is not one a write may
2273            // name. Leaving it in the input type moved the refusal to the
2274            // database, which answers `cannot insert a non-DEFAULT value into
2275            // column "id"` and has by then forgotten which argument the value
2276            // came from. Out of the type, the walk over the request says
2277            // `field 'id' not found in type: 'author_insert_input'` and can
2278            // still point at it.
2279            object
2280                .table
2281                .get_column(column)
2282                .is_some_and(|c| !c.always_generated)
2283                && allowed.is_none_or(|set| set.allows(column))
2284        })
2285        .collect()
2286}
2287
2288/// Whether a type has an `_inc_input` and a set of document operator inputs.
2289fn update_inputs(
2290    object: &crate::schema::object::TableObjectType,
2291    names: &crate::names::NameOverrides,
2292    role: Option<&str>,
2293) -> (bool, bool) {
2294    let fields = write_fields(object, names, role, crate::role::Verb::Update);
2295    (
2296        fields
2297            .iter()
2298            .any(|f| crate::schema::aggregate::is_incrementable(&f.graphql_type)),
2299        fields
2300            .iter()
2301            .any(|f| matches!(&f.graphql_type, crate::types::GraphQLType::Json)),
2302    )
2303}
2304
2305fn create_mutation_type(
2306    generated: &GeneratedSchema,
2307    root_name: &str,
2308    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2309    type_names: Arc<HashMap<(String, String), String>>,
2310    names: Arc<crate::names::NameOverrides>,
2311    max_rows: Option<i64>,
2312    role: Option<&str>,
2313) -> Object {
2314    let mut mutation = Object::new(root_name);
2315    // In name order, for the reason the query root is: see there.
2316    let mut roots: Vec<(String, Field)> = Vec::new();
2317
2318    // Only a table with a unique constraint has a conflict to name, and only
2319    // those got the types for it.
2320    // Which tables have something to add to, and which have a document to
2321    // operate on. Read from what was registered above rather than worked out
2322    // again from the object types: the second answer is the one that can
2323    // drift, and drifting means an argument naming a type that is not there.
2324    let mut has_numeric_column: HashSet<String> = HashSet::new();
2325    let mut has_jsonb_column: HashSet<String> = HashSet::new();
2326    for object in generated.object_types.values() {
2327        let (numeric, jsonb) = update_inputs(object, &names, role);
2328        if numeric {
2329            has_numeric_column.insert(object.local_name.clone());
2330        }
2331        if jsonb {
2332            has_jsonb_column.insert(object.local_name.clone());
2333        }
2334    }
2335
2336    let has_conflict_target: HashSet<String> = generated
2337        .object_types
2338        .iter()
2339        .filter(|(_, object)| !object.table.unique_constraints.is_empty())
2340        .map(|(_, object)| object.local_name.clone())
2341        .collect();
2342
2343    for field in &generated.mutation_fields {
2344        let table_name = field.table_name.clone();
2345        let schema_name = field.schema_name.clone();
2346        let mutation_type = field.mutation_type;
2347        let pk_columns = field.pk_columns.clone();
2348        let return_type = graphql_type_ref(&field.return_type);
2349
2350        // The table's own type name, which is what its boolean expression is
2351        // named after. A bulk mutation returns `[author!]!` and a by-key one
2352        // returns `author`; both name the same table.
2353        let where_type = field.local_type_name.clone();
2354
2355        let resolver_pk_columns = pk_columns.clone();
2356        let field_relationships = Arc::clone(&relationships);
2357        let field_type_names = Arc::clone(&type_names);
2358        let field_names = Arc::clone(&names);
2359        let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2360            let table_name = table_name.clone();
2361            let schema_name = schema_name.clone();
2362            let pk_columns = resolver_pk_columns.clone();
2363            let relationships = Arc::clone(&field_relationships);
2364            let type_names = Arc::clone(&field_type_names);
2365            let names = Arc::clone(&field_names);
2366            FieldFuture::new(async move {
2367                resolve_mutation(
2368                    &ctx,
2369                    &schema_name,
2370                    &table_name,
2371                    mutation_type,
2372                    &pk_columns,
2373                    &relationships,
2374                    &type_names,
2375                    &names,
2376                    max_rows,
2377                )
2378                .await
2379            })
2380        });
2381
2382        // Add mutation-specific arguments.
2383        //
2384        // A by-PK mutation takes the key columns rather than a `where` object:
2385        // it is meant to address exactly one row, and accepting `where` made it
2386        // an ordinary bulk mutation that happened to return the first result.
2387        match mutation_type {
2388            MutationType::Insert | MutationType::InsertOne => {
2389                let insert_input = format!("{}_insert_input", where_type);
2390                gql_field = if mutation_type == MutationType::Insert {
2391                    gql_field.argument(InputValue::new(
2392                        "objects",
2393                        TypeRef::named_nn_list_nn(&insert_input),
2394                    ))
2395                } else {
2396                    // A single insert takes `object`, not a one-element
2397                    // `objects`.
2398                    gql_field.argument(InputValue::new("object", TypeRef::named_nn(&insert_input)))
2399                };
2400                if has_conflict_target.contains(&where_type) {
2401                    gql_field = gql_field.argument(InputValue::new(
2402                        "on_conflict",
2403                        TypeRef::named(format!("{}_on_conflict", where_type)),
2404                    ));
2405                }
2406            }
2407            MutationType::UpdateByPk => {
2408                gql_field = gql_field.argument(InputValue::new(
2409                    "pk_columns",
2410                    TypeRef::named_nn(format!("{}_pk_columns_input", where_type)),
2411                ));
2412                gql_field = with_update_operators(
2413                    gql_field,
2414                    &where_type,
2415                    has_numeric_column.contains(&where_type),
2416                    has_jsonb_column.contains(&where_type),
2417                );
2418            }
2419            MutationType::Update => {
2420                gql_field = gql_field.argument(InputValue::new(
2421                    "where",
2422                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2423                ));
2424                gql_field = with_update_operators(
2425                    gql_field,
2426                    &where_type,
2427                    has_numeric_column.contains(&where_type),
2428                    has_jsonb_column.contains(&where_type),
2429                );
2430            }
2431            MutationType::UpdateMany => {
2432                gql_field = gql_field.argument(InputValue::new(
2433                    "updates",
2434                    TypeRef::named_nn_list_nn(format!("{}_updates", where_type)),
2435                ));
2436            }
2437            MutationType::DeleteByPk => {
2438                for (col_name, pg_type) in &pk_columns {
2439                    gql_field = gql_field.argument(InputValue::new(
2440                        names
2441                            .column(&field.schema_name, &field.table_name, col_name)
2442                            .unwrap_or(col_name),
2443                        TypeRef::named_nn(exposed_pk_argument_type(
2444                            names.as_ref(),
2445                            &field.schema_name,
2446                            &field.table_name,
2447                            col_name,
2448                            pg_type,
2449                        )),
2450                    ));
2451                }
2452            }
2453            MutationType::Delete => {
2454                // Required, as it is on an update: a delete with no predicate
2455                // is a delete of the whole table, and this refused one at
2456                // execution while the schema said it was a query worth
2457                // writing. Saying so in the type is what Hasura does, and it
2458                // is what a client's own tooling can catch before the request
2459                // is sent.
2460                gql_field = gql_field.argument(InputValue::new(
2461                    "where",
2462                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2463                ));
2464            }
2465        }
2466
2467        if let Some(desc) = &field.description {
2468            gql_field = gql_field.description(desc);
2469        }
2470
2471        roots.push((field.name.clone(), gql_field));
2472    }
2473
2474    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2475    for (_, field) in roots {
2476        mutation = mutation.field(field);
2477    }
2478
2479    mutation
2480}
2481
2482/// Create the Subscription type: the query root, answered again on change.
2483///
2484/// A subscription here is a **live query**. It carries the same fields the
2485/// query root does, takes the same arguments, and answers with the same rows
2486/// -- the difference is that the answer is sent again whenever it stops being
2487/// true. That is the contract a client generated against Hasura expects, and
2488/// the reason its `subscription_root` is a mirror of its `query_root`.
2489///
2490/// What wakes it is this server's own: a trigger publishes on the changed
2491/// table and the query is read again, which costs nothing while nothing is
2492/// written and answers in milliseconds when something is. A trigger cannot
2493/// see everything a query can -- a view has none, an embedded row may live in
2494/// a table that carries none, and `where: {expires_at: {_lt: "now()"}}`
2495/// changes with no write at all -- so a slow refresh runs beside it and
2496/// closes exactly those gaps. Polling alone is what Hasura does, and it pays
2497/// for every subscriber every tick whether or not anything happened.
2498fn create_subscription_type(
2499    generated: &GeneratedSchema,
2500    root_name: &str,
2501    max_rows: Option<i64>,
2502    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2503    names: Arc<crate::names::NameOverrides>,
2504    refresh: std::time::Duration,
2505) -> Subscription {
2506    let mut roots: Vec<(String, SubscriptionField)> = Vec::new();
2507
2508    for field in &generated.query_fields {
2509        let spec_type_name = field.local_type_name.clone();
2510        let pk_columns = field.pk_columns.clone();
2511        let is_by_pk = field.is_by_pk;
2512        let return_type = graphql_type_ref(&field.return_type);
2513        let watched = vec![crate::subscription::table_channel_name(
2514            &field.schema_name,
2515            &field.table_name,
2516        )];
2517
2518        let spec = Arc::new(QueryFieldSpec {
2519            schema_name: field.schema_name.clone(),
2520            table_name: field.table_name.clone(),
2521            type_name: field.type_name.clone(),
2522            is_by_pk,
2523            pk_columns: pk_columns.clone(),
2524            max_rows,
2525            relationships: Arc::clone(&relationships),
2526            names: Arc::clone(&names),
2527            call: None,
2528        });
2529
2530        let mut gql_field = SubscriptionField::new(&field.name, return_type, move |ctx| {
2531            let spec = Arc::clone(&spec);
2532            let watched = watched.clone();
2533            SubscriptionFieldFuture::new(async move {
2534                let wake = wake_on(&ctx, &watched, refresh).await;
2535                Ok(live(ctx, LiveSource::Rows(spec), wake))
2536            })
2537        });
2538
2539        gql_field = match is_by_pk {
2540            false => with_row_arguments(gql_field, &spec_type_name),
2541            true => with_key_arguments(
2542                gql_field,
2543                &field.schema_name,
2544                &field.table_name,
2545                &pk_columns,
2546                names.as_ref(),
2547            ),
2548        };
2549        if let Some(desc) = &field.description {
2550            gql_field = gql_field.description(desc);
2551        }
2552        // No row type, no root that answers with one -- see the query root.
2553        if field.rows {
2554            roots.push((field.name.clone(), gql_field));
2555        }
2556
2557        // A live query mirrors the query root, so an aggregate the role may not
2558        // ask for once is not one it may ask for continuously either.
2559        if is_by_pk || !field.aggregates {
2560            continue;
2561        }
2562
2563        // The same rows, with numbers about them, watched the same way.
2564        let agg_spec = Arc::new(AggregateSpec {
2565            schema_name: field.schema_name.clone(),
2566            table_name: field.table_name.clone(),
2567            type_name: field.type_name.clone(),
2568            max_rows,
2569            relationships: Arc::clone(&relationships),
2570            names: Arc::clone(&names),
2571            call: None,
2572        });
2573        let agg_watched = vec![crate::subscription::table_channel_name(
2574            &field.schema_name,
2575            &field.table_name,
2576        )];
2577        let agg_name = field.aggregate_name.clone().unwrap_or_else(|| {
2578            crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2579        });
2580        let mut agg_field = SubscriptionField::new(
2581            agg_name.clone(),
2582            TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2583                &field.local_type_name,
2584            )),
2585            move |ctx| {
2586                let agg_spec = Arc::clone(&agg_spec);
2587                let watched = agg_watched.clone();
2588                SubscriptionFieldFuture::new(async move {
2589                    let wake = wake_on(&ctx, &watched, refresh).await;
2590                    Ok(live(ctx, LiveSource::Aggregate(agg_spec), wake))
2591                })
2592            },
2593        );
2594        agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2595        agg_field = match field.aggregate_description.as_deref() {
2596            Some("") => agg_field,
2597            Some(given) => agg_field.description(given),
2598            None => agg_field.description(format!(
2599                "fetch aggregated fields from the table: \"{}\"",
2600                field.table_name
2601            )),
2602        };
2603        roots.push((agg_name, agg_field));
2604    }
2605
2606    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2607    let mut subscription = Subscription::new(root_name);
2608    // A GraphQL object may not have no fields, and a schema that exposes no
2609    // table still has a subscription root if subscriptions are on.
2610    if roots.is_empty() {
2611        return subscription.field(SubscriptionField::new(
2612            "no_subscriptions_available",
2613            TypeRef::named("String"),
2614            |_| {
2615                SubscriptionFieldFuture::new(async move {
2616                    Ok(futures::stream::empty::<
2617                        Result<FieldValue, async_graphql::Error>,
2618                    >())
2619                })
2620            },
2621        ));
2622    }
2623    for (_, field) in roots {
2624        subscription = subscription.field(field);
2625    }
2626    subscription
2627}
2628
2629/// What tells a live query its answer may have changed.
2630///
2631/// Every notification on the tables it reads, and a slow tick beside them.
2632/// Yields once per wake and never ends -- a subscription lasts as long as the
2633/// client holds it open.
2634async fn wake_on(
2635    ctx: &ResolverContext<'_>,
2636    channels: &[String],
2637    refresh: std::time::Duration,
2638) -> futures::stream::BoxStream<'static, ()> {
2639    use futures::StreamExt;
2640
2641    let mut streams: Vec<futures::stream::BoxStream<'static, ()>> =
2642        Vec::with_capacity(channels.len() + 1);
2643    // A tick even where nothing is listening: a table with no trigger, a view,
2644    // and a predicate that changes with the clock all depend on it.
2645    streams.push(
2646        tokio_stream::wrappers::IntervalStream::new({
2647            let mut interval = tokio::time::interval(refresh);
2648            interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
2649            interval
2650        })
2651        .map(|_| ())
2652        .boxed(),
2653    );
2654
2655    if let Ok(broker_arc) = ctx.data::<Arc<RwLock<Option<NotifyBroker>>>>() {
2656        let guard = broker_arc.read().await;
2657        if let Some(broker) = guard.as_ref() {
2658            for channel in channels {
2659                // `subscribe_or_create` rather than `subscribe`: a table whose
2660                // trigger was never installed has no channel, and a live query
2661                // on it is answered by the refresh rather than refused.
2662                streams.push(
2663                    broker
2664                        .subscribe_or_create(channel)
2665                        .await
2666                        .map(|_| ())
2667                        .boxed(),
2668                );
2669            }
2670        }
2671    }
2672
2673    futures::stream::select_all(streams).boxed()
2674}
2675
2676/// What a live query reads each time it is woken.
2677///
2678/// An enum rather than a closure because the answer borrows the context it is
2679/// read from, and a closure returning a future that borrows its argument is
2680/// not something a bound can say.
2681enum LiveSource {
2682    Rows(Arc<QueryFieldSpec>),
2683    Aggregate(Arc<AggregateSpec>),
2684}
2685
2686/// Read one answer, and the value to compare it against the last one by.
2687async fn read_live<'a>(
2688    ctx: &ResolverContext<'a>,
2689    source: &LiveSource,
2690) -> Result<(serde_json::Value, Option<FieldValue<'a>>), async_graphql::Error> {
2691    match source {
2692        LiveSource::Rows(spec) => {
2693            let rows = query_rows(ctx, spec).await?;
2694            Ok((
2695                serde_json::Value::Array(rows.clone()),
2696                rows_as_field_value(rows, spec.is_by_pk),
2697            ))
2698        }
2699        LiveSource::Aggregate(spec) => {
2700            let value = aggregate_value(ctx, spec).await?;
2701            let seen = serde_json::to_value(&value).unwrap_or_default();
2702            Ok((seen, Some(FieldValue::value(value))))
2703        }
2704    }
2705}
2706
2707/// A live query: the answer now, and the answer again each time it changes.
2708///
2709/// The first item is sent as soon as the client subscribes, which is what
2710/// makes this a query rather than a feed -- there is no window in which the
2711/// client knows nothing. After that, `answer` is read again on every wake and
2712/// sent only when what came back is not what was sent last: a write that
2713/// changes a row the subscription does not select is a wake and not a message.
2714fn live<'a>(
2715    ctx: ResolverContext<'a>,
2716    source: LiveSource,
2717    wake: futures::stream::BoxStream<'static, ()>,
2718) -> impl futures::Stream<Item = Result<FieldValue<'a>, async_graphql::Error>> + Send + 'a {
2719    use futures::StreamExt;
2720
2721    struct Live<'a> {
2722        ctx: ResolverContext<'a>,
2723        source: LiveSource,
2724        wake: futures::stream::BoxStream<'static, ()>,
2725        sent: Option<serde_json::Value>,
2726        started: bool,
2727        /// An error has been sent, and the subscription ends after it.
2728        failed: bool,
2729    }
2730
2731    futures::stream::unfold(
2732        Live {
2733            ctx,
2734            source,
2735            wake,
2736            sent: None,
2737            started: false,
2738            failed: false,
2739        },
2740        |mut live: Live<'a>| async move {
2741            if live.failed {
2742                return None;
2743            }
2744            loop {
2745                if live.started {
2746                    live.wake.next().await?;
2747                } else {
2748                    live.started = true;
2749                }
2750                let read = read_live(&live.ctx, &live.source).await;
2751                match read {
2752                    // An error ends the subscription: the client asked
2753                    // something that cannot be answered, and answering it
2754                    // again on the next tick would only repeat that. It is
2755                    // sent first, though -- ending on it unsent, as a
2756                    // `take_while(is_ok)` here once did, answers a refused
2757                    // subscription with a bare `complete` that reads exactly
2758                    // like one that finished.
2759                    Err(e) => {
2760                        live.failed = true;
2761                        return Some((Err(e), live));
2762                    }
2763                    Ok((seen, value)) => {
2764                        if live.sent.as_ref() == Some(&seen) {
2765                            continue;
2766                        }
2767                        live.sent = Some(seen);
2768                        let value = value.unwrap_or_else(|| FieldValue::value(Value::Null));
2769                        return Some((Ok(value), live));
2770                    }
2771                }
2772            }
2773        },
2774    )
2775}
2776
2777/// Everything a query field's resolver needs about the field it serves.
2778struct QueryFieldSpec {
2779    schema_name: String,
2780    table_name: String,
2781    type_name: String,
2782    is_by_pk: bool,
2783    pk_columns: Vec<(String, String)>,
2784    max_rows: Option<i64>,
2785    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2786    names: Arc<crate::names::NameOverrides>,
2787    /// Set where the rows come from a function rather than from the table
2788    /// itself. Everything else about reading them is the same.
2789    call: Option<Arc<FunctionCall>>,
2790}
2791
2792/// Everything `_entities` needs to resolve a representation to a table row.
2793#[derive(Clone)]
2794struct EntityLookup {
2795    schema_name: String,
2796    table_name: String,
2797    type_name: String,
2798    key_columns: Vec<EntityKeyColumn>,
2799    row_column_types: HashMap<String, String>,
2800}
2801
2802#[derive(Clone)]
2803struct EntityKeyColumn {
2804    field_name: String,
2805    column_name: String,
2806    pg_type: String,
2807    explicit_id: bool,
2808}
2809
2810/// The federation entity resolver's immutable schema view.
2811struct EntityResolverState {
2812    entities: HashMap<String, EntityLookup>,
2813    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2814    names: Arc<crate::names::NameOverrides>,
2815    max_rows: Option<i64>,
2816}
2817
2818impl EntityResolverState {
2819    fn new(
2820        generated: &GeneratedSchema,
2821        relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2822        names: Arc<crate::names::NameOverrides>,
2823        max_rows: Option<i64>,
2824    ) -> Self {
2825        let mut entities = HashMap::new();
2826        for (type_name, entity) in &generated.federation_entities {
2827            let Some(object) = generated.object_types.get(type_name) else {
2828                continue;
2829            };
2830            let mut key_columns = Vec::with_capacity(entity.key_fields.len());
2831            for field_name in &entity.key_fields {
2832                let column_name = names
2833                    .column_source(&object.table.schema, &object.table.name, field_name)
2834                    .unwrap_or(field_name);
2835                let Some(column) = object.table.get_column(column_name) else {
2836                    continue;
2837                };
2838                key_columns.push(EntityKeyColumn {
2839                    field_name: field_name.clone(),
2840                    column_name: column.name.clone(),
2841                    pg_type: column.nominal_type.clone(),
2842                    explicit_id: matches!(
2843                        names.column_type(&object.table.schema, &object.table.name, &column.name),
2844                        Some(crate::types::GraphQLType::Id)
2845                    ),
2846                });
2847            }
2848            if key_columns.len() != entity.key_fields.len() {
2849                continue;
2850            }
2851            entities.insert(
2852                type_name.clone(),
2853                EntityLookup {
2854                    schema_name: object.table.schema.clone(),
2855                    table_name: object.table.name.clone(),
2856                    type_name: type_name.clone(),
2857                    key_columns,
2858                    row_column_types: exposed_column_types(&object.table, names.as_ref()),
2859                },
2860            );
2861        }
2862        Self {
2863            entities,
2864            relationships,
2865            names,
2866            max_rows,
2867        }
2868    }
2869}
2870
2871struct EntityRepresentation {
2872    index: usize,
2873    type_name: String,
2874    values: serde_json::Map<String, serde_json::Value>,
2875}
2876
2877/// Fields selected for one concrete member of the federation `_Entity` union.
2878///
2879/// `SelectionField::selection_set()` expands every fragment but does not apply
2880/// its type condition. Keep a parallel applicability list while walking the
2881/// raw selection tree, then use it to retain only the fields meant for this
2882/// entity type.
2883fn entity_field_applicability(
2884    selection_set: &SelectionSet,
2885    fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2886    type_name: &str,
2887) -> Vec<bool> {
2888    fn condition_matches(condition: Option<&Positioned<TypeCondition>>, type_name: &str) -> bool {
2889        condition.is_none_or(|condition| {
2890            let condition = condition.node.on.node.as_str();
2891            condition == type_name || condition == "_Entity"
2892        })
2893    }
2894
2895    fn collect(
2896        selection_set: &SelectionSet,
2897        fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2898        type_name: &str,
2899        parent_matches: bool,
2900        applicability: &mut Vec<bool>,
2901    ) {
2902        for selection in &selection_set.items {
2903            match &selection.node {
2904                Selection::Field(_) => applicability.push(parent_matches),
2905                Selection::FragmentSpread(spread) => {
2906                    let Some(fragment) = fragments.get(&spread.node.fragment_name.node) else {
2907                        continue;
2908                    };
2909                    collect(
2910                        &fragment.node.selection_set.node,
2911                        fragments,
2912                        type_name,
2913                        parent_matches
2914                            && condition_matches(Some(&fragment.node.type_condition), type_name),
2915                        applicability,
2916                    );
2917                }
2918                Selection::InlineFragment(fragment) => collect(
2919                    &fragment.node.selection_set.node,
2920                    fragments,
2921                    type_name,
2922                    parent_matches
2923                        && condition_matches(fragment.node.type_condition.as_ref(), type_name),
2924                    applicability,
2925                ),
2926            }
2927        }
2928    }
2929
2930    let mut applicability = Vec::new();
2931    collect(
2932        selection_set,
2933        fragments,
2934        type_name,
2935        true,
2936        &mut applicability,
2937    );
2938    applicability
2939}
2940
2941fn entity_selection_fields<'ctx, 'a>(
2942    ctx: &'ctx ResolverContext<'a>,
2943    type_name: &str,
2944) -> Vec<SelectionField<'ctx>> {
2945    let applicability = entity_field_applicability(
2946        &ctx.ctx.item.node.selection_set.node,
2947        &ctx.ctx.query_env.fragments,
2948        type_name,
2949    );
2950    let fields: Vec<_> = ctx.field().selection_set().collect();
2951    debug_assert_eq!(fields.len(), applicability.len());
2952    fields
2953        .into_iter()
2954        .zip(applicability)
2955        .filter_map(|(field, applies)| applies.then_some(field))
2956        .collect()
2957}
2958
2959async fn resolve_entities<'a>(
2960    ctx: &ResolverContext<'a>,
2961    state: &EntityResolverState,
2962) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
2963    let representations = ctx.args.try_get("representations")?.list()?;
2964    let mut grouped: HashMap<String, Vec<EntityRepresentation>> = HashMap::new();
2965    let mut count = 0;
2966    for (index, representation) in representations.iter().enumerate() {
2967        count += 1;
2968        let value = accessor_to_json(&representation);
2969        let serde_json::Value::Object(values) = value else {
2970            return Err(async_graphql::Error::new(
2971                "entity representation must be an object",
2972            ));
2973        };
2974        let type_name = values
2975            .get("__typename")
2976            .and_then(serde_json::Value::as_str)
2977            .ok_or_else(|| {
2978                async_graphql::Error::new("entity representation is missing __typename")
2979            })?
2980            .to_string();
2981        grouped
2982            .entry(type_name.clone())
2983            .or_default()
2984            .push(EntityRepresentation {
2985                index,
2986                type_name,
2987                values,
2988            });
2989    }
2990
2991    let mut resolved: Vec<Option<FieldValue<'a>>> =
2992        std::iter::repeat_with(|| None).take(count).collect();
2993    for (type_name, representations) in grouped {
2994        let lookup = state.entities.get(&type_name).ok_or_else(|| {
2995            async_graphql::Error::new(format!("unknown federation entity \"{}\"", type_name))
2996        })?;
2997        let rows = resolve_entity_group(ctx, lookup, &representations, state).await?;
2998        for (ordinality, representation) in representations.into_iter().enumerate() {
2999            resolved[representation.index] = Some(match &rows[ordinality] {
3000                Some(row) => FieldValue::value(json_to_value(row.clone()))
3001                    .with_type(representation.type_name),
3002                None => FieldValue::NULL,
3003            });
3004        }
3005    }
3006
3007    Ok(Some(FieldValue::list(resolved.into_iter().map(|value| {
3008        value.expect("every representation was resolved or errored")
3009    }))))
3010}
3011
3012async fn resolve_entity_group(
3013    ctx: &ResolverContext<'_>,
3014    lookup: &EntityLookup,
3015    representations: &[EntityRepresentation],
3016    state: &EntityResolverState,
3017) -> Result<Vec<Option<serde_json::Value>>, async_graphql::Error> {
3018    if representations.is_empty() {
3019        return Ok(Vec::new());
3020    }
3021
3022    let pool = ctx.data::<PgPool>()?;
3023    let gql_ctx = ctx.data::<GraphQLContext>()?;
3024    let mut bound_values = Vec::new();
3025    let mut value_rows = Vec::with_capacity(representations.len());
3026    for (ordinality, representation) in representations.iter().enumerate() {
3027        let normalized_values = normalized_entity_key_values(
3028            &representation.values,
3029            &lookup.key_columns,
3030            &lookup.type_name,
3031        )?;
3032        let mut values = Vec::with_capacity(lookup.key_columns.len() + 1);
3033        for (key_column, value) in lookup.key_columns.iter().zip(normalized_values) {
3034            values.push(format!(
3035                "${}::{}",
3036                bound_values.len() + 1,
3037                key_column.pg_type
3038            ));
3039            bound_values.push(value);
3040        }
3041        values.push(ordinality.to_string());
3042        value_rows.push(format!("({})", values.join(", ")));
3043    }
3044
3045    let representation_alias = "pgrst_entity_representation";
3046    let ordinality_column = "pgrst_entity_ordinality";
3047    let key_aliases: Vec<String> = (0..lookup.key_columns.len())
3048        .map(|index| format!("pgrst_entity_key_{index}"))
3049        .collect();
3050    let mut representation_columns: Vec<String> = key_aliases
3051        .iter()
3052        .map(|column| postrust_sql::escape_ident(column))
3053        .collect();
3054    representation_columns.push(postrust_sql::escape_ident(ordinality_column));
3055    let representations_sql = format!(
3056        "(VALUES {}) AS {} ({})",
3057        value_rows.join(", "),
3058        postrust_sql::escape_ident(representation_alias),
3059        representation_columns.join(", ")
3060    );
3061    let key_predicate = lookup
3062        .key_columns
3063        .iter()
3064        .zip(&key_aliases)
3065        .map(|(key_column, key_alias)| {
3066            format!(
3067                "{}.{} = {}.{}",
3068                postrust_sql::escape_ident(READ_ROW),
3069                postrust_sql::escape_ident(&key_column.column_name),
3070                postrust_sql::escape_ident(representation_alias),
3071                postrust_sql::escape_ident(key_alias)
3072            )
3073        })
3074        .collect::<Vec<_>>()
3075        .join(" AND ");
3076    let mut where_sql = format!(" WHERE ({key_predicate})");
3077
3078    let permission = permission_predicate(
3079        &gql_ctx.caller(),
3080        state.names.as_ref(),
3081        &lookup.schema_name,
3082        &lookup.table_name,
3083    )?;
3084    if let Some(predicate) = permission {
3085        let guard = gql_ctx
3086            .schema_cache
3087            .get()
3088            .await
3089            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3090        let cache = guard
3091            .as_ref()
3092            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3093        let scope = WhereScope::table(
3094            &lookup.schema_name,
3095            &lookup.table_name,
3096            &lookup.type_name,
3097            state.names.as_ref(),
3098        )
3099        .under_alias(READ_ROW)
3100        .with_resolution(cache, state.relationships.as_ref())
3101        .for_caller(gql_ctx.caller());
3102        let (filter_sql, filter_values) =
3103            build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3104        if !filter_sql.is_empty() {
3105            where_sql = format!(
3106                "{} AND ({})",
3107                where_sql,
3108                filter_sql.trim_start_matches("WHERE ")
3109            );
3110            bound_values.extend(filter_values);
3111        }
3112    }
3113
3114    let source = format!(
3115        "{}.{} AS {}",
3116        postrust_sql::escape_ident(&lookup.schema_name),
3117        postrust_sql::escape_ident(&lookup.table_name),
3118        postrust_sql::escape_ident(READ_ROW)
3119    );
3120    let source_rows = format!(
3121        "SELECT {}.* FROM {}{}",
3122        postrust_sql::escape_ident(READ_ROW),
3123        source,
3124        where_sql
3125    );
3126    let mut projection = {
3127        let guard = gql_ctx
3128            .schema_cache
3129            .get()
3130            .await
3131            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3132        guard
3133            .as_ref()
3134            .and_then(|cache| {
3135                cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3136                    &lookup.schema_name,
3137                    &lookup.table_name,
3138                ))
3139            })
3140            .and_then(|table| rename_projection(table, "src", state.names.as_ref()))
3141            .unwrap_or_else(|| "src.*".to_string())
3142    };
3143    let projected_rows = {
3144        let guard = gql_ctx
3145            .schema_cache
3146            .get()
3147            .await
3148            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3149        let cache = guard
3150            .as_ref()
3151            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3152        let table = cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3153            &lookup.schema_name,
3154            &lookup.table_name,
3155        ));
3156        let selected_fields = entity_selection_fields(ctx, &lookup.type_name);
3157        let mut param_idx = bound_values.len() + 1;
3158        let computed = match table {
3159            Some(table) => computed_projections(
3160                table,
3161                selected_fields.iter().copied(),
3162                "src",
3163                state.names.as_ref(),
3164                cache,
3165                &mut param_idx,
3166                &mut bound_values,
3167            )?,
3168            None => Vec::new(),
3169        };
3170        let embeds = build_embed_expressions(
3171            &gql_ctx.caller(),
3172            cache,
3173            state.relationships.as_ref(),
3174            &lookup.type_name,
3175            "src",
3176            selected_fields,
3177            state.max_rows,
3178            &mut 0,
3179            &mut param_idx,
3180            &mut bound_values,
3181            state.names.as_ref(),
3182        )?;
3183        for expression in &computed {
3184            projection.push_str(", ");
3185            projection.push_str(expression);
3186        }
3187        for (field_name, expression) in &embeds {
3188            projection.push_str(", ");
3189            projection.push_str(expression);
3190            projection.push_str(" AS ");
3191            projection.push_str(&postrust_sql::escape_ident(field_name));
3192        }
3193        format!("SELECT {} FROM ({}) AS src", projection, source_rows)
3194    };
3195    // PostgreSQL decides whether an entity key matches. Carry the input's
3196    // ordinal through that same query instead of reconstructing a key from
3197    // row JSON: PostgreSQL-equivalent spellings (UUID case, citext case and
3198    // timestamptz offsets, for example) need to resolve to the row it found.
3199    let sql = format!(
3200        "SELECT json_build_array({}.{}, {}) FROM {} CROSS JOIN LATERAL ({}) AS t",
3201        postrust_sql::escape_ident(representation_alias),
3202        postrust_sql::escape_ident(ordinality_column),
3203        row_json("t", &lookup.row_column_types),
3204        representations_sql,
3205        projected_rows,
3206    );
3207    let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3208    let rows = execute_query_on(&mut tx, &sql, &bound_values).await?;
3209    tx.commit().await?;
3210
3211    let mut by_ordinal = std::iter::repeat_with(|| None)
3212        .take(representations.len())
3213        .collect::<Vec<_>>();
3214    for result in rows {
3215        let serde_json::Value::Array(mut pair) = result else {
3216            return Err(async_graphql::Error::new(
3217                "entity query returned an invalid result",
3218            ));
3219        };
3220        if pair.len() != 2 {
3221            return Err(async_graphql::Error::new(
3222                "entity query returned an invalid result",
3223            ));
3224        }
3225        let row = pair.pop().expect("the result pair has two values");
3226        let ordinality = pair
3227            .pop()
3228            .and_then(|value| value.as_u64())
3229            .and_then(|value| usize::try_from(value).ok())
3230            .filter(|value| *value < by_ordinal.len())
3231            .ok_or_else(|| async_graphql::Error::new("entity query returned an invalid ordinal"))?;
3232        by_ordinal[ordinality] = Some(row);
3233    }
3234    Ok(by_ordinal)
3235}
3236
3237/// Validate an entity key and normalize explicit GraphQL `ID` overrides to
3238/// their integer, text or UUID storage type. `_Any` deliberately skips the
3239/// field-level checks ordinary GraphQL arguments receive.
3240fn normalized_entity_key_values(
3241    values: &serde_json::Map<String, serde_json::Value>,
3242    key_columns: &[EntityKeyColumn],
3243    type_name: &str,
3244) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
3245    let mut key = Vec::with_capacity(key_columns.len());
3246    for key_column in key_columns {
3247        let value = values.get(&key_column.field_name).ok_or_else(|| {
3248            async_graphql::Error::new(format!(
3249                "entity representation for \"{}\" is missing key field \"{}\"",
3250                type_name, key_column.field_name
3251            ))
3252        })?;
3253        key.push(normalize_entity_key_value(type_name, key_column, value)?);
3254    }
3255    Ok(key)
3256}
3257
3258fn normalize_entity_key_value(
3259    type_name: &str,
3260    key_column: &EntityKeyColumn,
3261    value: &serde_json::Value,
3262) -> Result<serde_json::Value, async_graphql::Error> {
3263    let invalid = |expected: &str| {
3264        async_graphql::Error::new(format!(
3265            "entity representation for \"{}\" has invalid key field \"{}\": expected {}, found {}",
3266            type_name,
3267            key_column.field_name,
3268            expected,
3269            json_value_kind(value)
3270        ))
3271    };
3272
3273    if key_column.explicit_id {
3274        return normalize_graphql_id(value, &key_column.pg_type).ok_or_else(|| {
3275            invalid(&format!(
3276                "an ID compatible with PostgreSQL type \"{}\"",
3277                key_column.pg_type
3278            ))
3279        });
3280    }
3281
3282    let pg_type = key_column.pg_type.trim().to_ascii_lowercase();
3283    let normalized = if matches!(pg_type.as_str(), "int8" | "bigint") {
3284        match value {
3285            serde_json::Value::Number(number) => number.as_i64(),
3286            serde_json::Value::String(text) => text.parse::<i64>().ok(),
3287            _ => None,
3288        }
3289        .map(serde_json::Value::from)
3290    } else if integer_bounds(&pg_type).is_some() {
3291        value.as_i64().and_then(|integer| {
3292            let (min, max) = integer_bounds(&pg_type)?;
3293            (min..=max)
3294                .contains(&integer)
3295                .then(|| serde_json::Value::from(integer))
3296        })
3297    } else if matches!(
3298        pg_type.as_str(),
3299        "float4" | "float8" | "real" | "double precision"
3300    ) {
3301        value
3302            .as_f64()
3303            .and_then(serde_json::Number::from_f64)
3304            .map(serde_json::Value::Number)
3305    } else if matches!(pg_type.as_str(), "numeric" | "decimal") {
3306        match value {
3307            serde_json::Value::Number(number) => {
3308                Some(serde_json::Value::String(number.to_string()))
3309            }
3310            serde_json::Value::String(text) if text.parse::<sqlx::types::BigDecimal>().is_ok() => {
3311                Some(serde_json::Value::String(text.clone()))
3312            }
3313            _ => None,
3314        }
3315    } else if matches!(pg_type.as_str(), "bool" | "boolean") {
3316        value.as_bool().map(serde_json::Value::Bool)
3317    } else if matches!(pg_type.as_str(), "json" | "jsonb") {
3318        Some(value.clone())
3319    } else if is_array_type(&pg_type) {
3320        value.is_array().then(|| value.clone())
3321    } else {
3322        value
3323            .as_str()
3324            .map(|text| serde_json::Value::String(text.to_string()))
3325    };
3326
3327    normalized.ok_or_else(|| {
3328        invalid(&format!(
3329            "a value compatible with PostgreSQL type \"{}\"",
3330            key_column.pg_type
3331        ))
3332    })
3333}
3334
3335fn normalize_graphql_id(value: &serde_json::Value, pg_type: &str) -> Option<serde_json::Value> {
3336    if integer_bounds(pg_type).is_some() {
3337        let integer = match value {
3338            serde_json::Value::Number(number) => number.as_i64(),
3339            serde_json::Value::String(text) => text.parse::<i64>().ok(),
3340            _ => None,
3341        }?;
3342        let (min, max) = integer_bounds(pg_type)?;
3343        return (min..=max)
3344            .contains(&integer)
3345            .then(|| serde_json::Value::from(integer));
3346    }
3347    if pg_type.eq_ignore_ascii_case("uuid") {
3348        return normalize_uuid(value).map(serde_json::Value::String);
3349    }
3350    if is_text_type(pg_type) {
3351        return match value {
3352            serde_json::Value::String(s) => Some(serde_json::Value::String(s.clone())),
3353            serde_json::Value::Number(n) if n.is_i64() || n.is_u64() => {
3354                Some(serde_json::Value::String(n.to_string()))
3355            }
3356            _ => None,
3357        };
3358    }
3359    None
3360}
3361
3362fn integer_bounds(pg_type: &str) -> Option<(i64, i64)> {
3363    match pg_type.trim().to_ascii_lowercase().as_str() {
3364        "int2" | "smallint" => Some((i16::MIN.into(), i16::MAX.into())),
3365        "int4" | "int" | "integer" => Some((i32::MIN.into(), i32::MAX.into())),
3366        "int8" | "bigint" => Some((i64::MIN, i64::MAX)),
3367        _ => None,
3368    }
3369}
3370
3371fn is_text_type(pg_type: &str) -> bool {
3372    matches!(
3373        pg_type.trim().to_ascii_lowercase().as_str(),
3374        "text" | "varchar" | "character varying" | "char" | "character" | "bpchar" | "citext"
3375    )
3376}
3377
3378fn normalize_uuid(value: &serde_json::Value) -> Option<String> {
3379    sqlx::types::Uuid::parse_str(value.as_str()?)
3380        .ok()
3381        .map(|uuid| uuid.to_string())
3382}
3383
3384fn json_value_kind(value: &serde_json::Value) -> &'static str {
3385    match value {
3386        serde_json::Value::Null => "null",
3387        serde_json::Value::Bool(_) => "a boolean",
3388        serde_json::Value::Number(_) => "a number",
3389        serde_json::Value::String(_) => "a string",
3390        serde_json::Value::Array(_) => "an array",
3391        serde_json::Value::Object(_) => "an object",
3392    }
3393}
3394
3395/// Add the root fields for functions that answer with rows of a table.
3396///
3397/// The same arguments a table's own root field takes, because the rows are the
3398/// same rows: what the function adds is `args`, which is where its own
3399/// arguments go so that a parameter called `limit` cannot shadow the one that
3400/// pages the result.
3401fn add_function_fields(
3402    mut root: Object,
3403    generated: &GeneratedSchema,
3404    volatile: bool,
3405    max_rows: Option<i64>,
3406    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3407    names: Arc<crate::names::NameOverrides>,
3408) -> Object {
3409    // The names the root already carries, from the tables. A function may be
3410    // named after one -- `create function t() returns setof t` is an ordinary
3411    // way to write a parameterised view -- and `Object::field` *panics* on a
3412    // duplicate, so an unguarded collision is a server that will not start.
3413    let taken: HashSet<&str> = if volatile {
3414        generated
3415            .mutation_fields
3416            .iter()
3417            .map(|f| f.name.as_str())
3418            .collect()
3419    } else {
3420        generated
3421            .query_fields
3422            .iter()
3423            .map(|f| f.name.as_str())
3424            .collect()
3425    };
3426
3427    for function in &generated.function_fields {
3428        if function.volatile != volatile {
3429            continue;
3430        }
3431        if taken.contains(function.name.as_str()) {
3432            tracing::warn!(
3433                "not exposing {}.{}({}) -- a table already has a root field \
3434                 called {}",
3435                function.schema_name,
3436                function.function_name,
3437                function.argument_signature(),
3438                function.name,
3439            );
3440            continue;
3441        }
3442        let spec = Arc::new(QueryFieldSpec {
3443            schema_name: function.returns_table.0.clone(),
3444            table_name: function.returns_table.1.clone(),
3445            type_name: function.returns.clone(),
3446            is_by_pk: false,
3447            pk_columns: Vec::new(),
3448            max_rows,
3449            relationships: Arc::clone(&relationships),
3450            names: Arc::clone(&names),
3451            call: Some(Arc::new(FunctionCall {
3452                schema: function.schema_name.clone(),
3453                name: function.function_name.clone(),
3454                arguments: function.arguments.clone(),
3455                session_argument: function.session_argument.clone(),
3456            })),
3457        });
3458
3459        let mut field = Field::new(
3460            &function.name,
3461            TypeRef::named_nn_list_nn(&function.returns),
3462            move |ctx| {
3463                let spec = Arc::clone(&spec);
3464                FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
3465            },
3466        );
3467        if !function.arguments.is_empty() {
3468            field = field.argument(InputValue::new(
3469                "args",
3470                TypeRef::named_nn(format!("{}_args", function.name)),
3471            ));
3472        }
3473        field = field
3474            .argument(InputValue::new(
3475                "where",
3476                TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
3477                    &function.local_returns,
3478                )),
3479            ))
3480            .argument(InputValue::new(
3481                "order_by",
3482                TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
3483                    &function.local_returns,
3484                )),
3485            ))
3486            .argument(InputValue::new(
3487                "distinct_on",
3488                TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
3489                    &function.local_returns,
3490                )),
3491            ))
3492            .argument(InputValue::new("limit", TypeRef::named("Int")))
3493            .argument(InputValue::new("offset", TypeRef::named("Int")));
3494        if let Some(description) = &function.description {
3495            field = field.description(description);
3496        }
3497        root = root.field(field);
3498
3499        // The same rows, with numbers about them. Every table root has one and
3500        // so does every function root: `search_tracks_aggregate(args: {…})`
3501        // counts what `search_tracks(args: {…})` would have answered with,
3502        // which means calling the function and taking the same arguments.
3503        let agg_spec = Arc::new(AggregateSpec {
3504            schema_name: function.returns_table.0.clone(),
3505            table_name: function.returns_table.1.clone(),
3506            type_name: function.returns.clone(),
3507            max_rows,
3508            relationships: Arc::clone(&relationships),
3509            names: Arc::clone(&names),
3510            call: Some(Arc::new(FunctionCall {
3511                schema: function.schema_name.clone(),
3512                name: function.function_name.clone(),
3513                arguments: function.arguments.clone(),
3514                session_argument: function.session_argument.clone(),
3515            })),
3516        });
3517        let mut aggregate = Field::new(
3518            format!("{}_aggregate", function.name),
3519            TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
3520                &function.local_returns,
3521            )),
3522            move |ctx| {
3523                let agg_spec = Arc::clone(&agg_spec);
3524                FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
3525            },
3526        );
3527        if !function.arguments.is_empty() {
3528            aggregate = aggregate.argument(InputValue::new(
3529                "args",
3530                TypeRef::named_nn(format!("{}_args", function.name)),
3531            ));
3532        }
3533        aggregate = with_row_arguments(aggregate, &function.local_returns).description(format!(
3534            "fetch aggregated fields from the table: \"{}\"",
3535            function.returns_table.1
3536        ));
3537        root = root.field(aggregate);
3538    }
3539    root
3540}
3541
3542/// The FROM entry for a root field that calls a function.
3543///
3544/// Aliased as the table the function answers with rows of, so a filter or an
3545/// ordering written against that table's columns finds them: without it the
3546/// FROM clause names the function and `article.score` is a table nobody
3547/// mentioned. Whatever the caller put under `args` is bound here, which is why
3548/// it takes the parameter list.
3549fn function_source(
3550    ctx: &ResolverContext<'_>,
3551    call: &FunctionCall,
3552    alias: &str,
3553    bound_values: &mut Vec<serde_json::Value>,
3554) -> Result<String, async_graphql::Error> {
3555    let given = ctx
3556        .args
3557        .try_get("args")
3558        .ok()
3559        .map(|v| accessor_to_json(&v))
3560        .unwrap_or_else(|| serde_json::json!({}));
3561    let mut passed = Vec::new();
3562    for (name, _, required) in &call.arguments {
3563        match given.get(name) {
3564            None if *required => {
3565                return Err(async_graphql::Error::new(format!(
3566                    "{} needs the argument \"{}\"",
3567                    call.name, name
3568                )))
3569            }
3570            // An argument left out is left out, so the function's own default
3571            // applies. Passing null instead would override it.
3572            None => continue,
3573            Some(value) => {
3574                // Named notation, so the arguments a client did give land on
3575                // the parameters it meant regardless of order. A GraphQL Int
3576                // binds as a bigint, and `f(article_id => bigint)` is not
3577                // `f(integer)` -- the function is looked up by the types of
3578                // what it was handed, so what it was handed has to be the
3579                // types it declares.
3580                let (_, pg_type, _) = call
3581                    .arguments
3582                    .iter()
3583                    .find(|(argument, _, _)| argument == name)
3584                    .expect("iterating the arguments");
3585                passed.push(format!(
3586                    "{} => ${}::{}",
3587                    postrust_sql::escape_ident(name),
3588                    bound_values.len() + 1,
3589                    pg_type
3590                ));
3591                bound_values.push(value.clone());
3592            }
3593        }
3594    }
3595    // The session argument is not the client's to send: it says who is
3596    // asking, and a caller that could write it could name any identity it
3597    // liked. It is filled here, from the verified token.
3598    if let Some(session) = &call.session_argument {
3599        passed.push(format!(
3600            "{} => {}",
3601            postrust_sql::escape_ident(session),
3602            SESSION_ARGUMENT
3603        ));
3604    }
3605    Ok(format!(
3606        "{}.{}({}) AS {}",
3607        postrust_sql::escape_ident(&call.schema),
3608        postrust_sql::escape_ident(&call.name),
3609        passed.join(", "),
3610        postrust_sql::escape_ident(alias)
3611    ))
3612}
3613
3614/// A function a root field calls instead of reading a table.
3615struct FunctionCall {
3616    schema: String,
3617    name: String,
3618    arguments: Vec<(String, String, bool)>,
3619    /// The parameter filled from the caller's session rather than from the
3620    /// request. See [`crate::schema::FunctionField::session_argument`].
3621    session_argument: Option<String>,
3622}
3623
3624/// Everything an aggregate field's resolver needs.
3625struct AggregateSpec {
3626    schema_name: String,
3627    table_name: String,
3628    type_name: String,
3629    max_rows: Option<i64>,
3630    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3631    names: Arc<crate::names::NameOverrides>,
3632    /// Set where the rows come from a function rather than from the table
3633    /// itself, exactly as it is on [`QueryFieldSpec`]. Counting the rows a
3634    /// function answers with means calling it.
3635    call: Option<Arc<FunctionCall>>,
3636}
3637
3638/// Resolve an aggregate field.
3639///
3640/// One pass over the selection decides what SQL to write: a client asking only
3641/// for `count` should not pay for the rows, and one asking only for `nodes`
3642/// should not pay for a second scan. Both halves read from the same filtered
3643/// subquery, so `aggregate` describes exactly the set `nodes` came from.
3644async fn resolve_aggregate<'a>(
3645    ctx: &ResolverContext<'a>,
3646    spec: &AggregateSpec,
3647) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3648    Ok(Some(FieldValue::value(aggregate_value(ctx, spec).await?)))
3649}
3650
3651/// The numbers an aggregate field answers with.
3652///
3653/// Split from the resolver for the reason [`query_rows`] is: a subscription
3654/// reads them over and over and has to tell one answer from the last.
3655async fn aggregate_value(
3656    ctx: &ResolverContext<'_>,
3657    spec: &AggregateSpec,
3658) -> Result<Value, async_graphql::Error> {
3659    use crate::schema::aggregate as agg;
3660
3661    let pool = ctx.data::<PgPool>()?;
3662    let gql_ctx = ctx.data::<GraphQLContext>()?;
3663
3664    let mut bound_values: Vec<serde_json::Value> = Vec::new();
3665    // A function's own arguments are bound before anything else, so a
3666    // predicate written beside them continues the numbering rather than
3667    // colliding with it.
3668    let source = match &spec.call {
3669        None => format!(
3670            "{}.{}",
3671            postrust_sql::escape_ident(&spec.schema_name),
3672            postrust_sql::escape_ident(&spec.table_name)
3673        ),
3674        Some(call) => function_source(ctx, call, &spec.table_name, &mut bound_values)?,
3675    };
3676
3677    let mut where_sql = String::new();
3678    {
3679        // Counting rows is reading them, so the permission applies here in the
3680        // same breath as the request's own filter -- otherwise `count` would
3681        // report a total the same role cannot list.
3682        let requested = ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v));
3683        let permission = permission_predicate(
3684            &gql_ctx.caller(),
3685            spec.names.as_ref(),
3686            &spec.schema_name,
3687            &spec.table_name,
3688        )?;
3689        if let Some(predicate) = and_predicates(requested, permission) {
3690            let guard = gql_ctx
3691                .schema_cache
3692                .get()
3693                .await
3694                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3695            let cache = guard
3696                .as_ref()
3697                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3698            // A function's rows are read under the table's name as an alias,
3699            // not from the table itself -- the same split the row read makes.
3700            // Counting them under the qualified name answered `invalid
3701            // reference to FROM-clause entry for table "Track"` the moment a
3702            // permission referred to a column, which is any permission that
3703            // follows a relationship.
3704            let scope = match &spec.call {
3705                None => WhereScope::table(
3706                    &spec.schema_name,
3707                    &spec.table_name,
3708                    &spec.type_name,
3709                    spec.names.as_ref(),
3710                )
3711                .with_resolution(cache, spec.relationships.as_ref()),
3712                Some(_) => WhereScope::for_alias(
3713                    &spec.schema_name,
3714                    &spec.table_name,
3715                    &spec.table_name,
3716                    &spec.type_name,
3717                    cache,
3718                    spec.relationships.as_ref(),
3719                    spec.names.as_ref(),
3720                ),
3721            }
3722            .for_caller(gql_ctx.caller());
3723            let (sql, values) =
3724                build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3725            if !sql.is_empty() {
3726                where_sql = format!(" {}", sql);
3727                bound_values.extend(values);
3728            }
3729        }
3730    }
3731
3732    let order_sql = build_order_by_clause(
3733        ctx,
3734        &gql_ctx.schema_cache,
3735        &spec.schema_name,
3736        &spec.table_name,
3737        &spec.type_name,
3738        spec.relationships.as_ref(),
3739        &format!(
3740            "{}.{}",
3741            postrust_sql::escape_ident(&spec.schema_name),
3742            postrust_sql::escape_ident(&spec.table_name)
3743        ),
3744        spec.names.as_ref(),
3745    )
3746    .await?;
3747
3748    let requested_limit = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
3749    let offset = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
3750    let ceiling = match (
3751        spec.max_rows,
3752        permission_limit(
3753            &gql_ctx.caller(),
3754            spec.names.as_ref(),
3755            &spec.schema_name,
3756            &spec.table_name,
3757        ),
3758    ) {
3759        (Some(configured), Some(granted)) => Some(configured.min(granted)),
3760        (only, None) | (None, only) => only,
3761    };
3762    let limit = match (requested_limit, ceiling) {
3763        (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
3764        (Some(requested), None) => Some(requested),
3765        (None, ceiling) => ceiling,
3766    };
3767
3768    let rows = format!("SELECT * FROM {}{}{}", source, where_sql, order_sql);
3769
3770    // What `nodes` reads: the page, bounded by whichever ceiling is lowest.
3771    let mut inner = rows.clone();
3772    if let Some(limit) = limit {
3773        inner.push_str(&format!(" LIMIT {}", limit));
3774    }
3775    if let Some(offset) = offset {
3776        inner.push_str(&format!(" OFFSET {}", offset));
3777    }
3778
3779    // What `aggregate` reads, which is not the same rows. A count is not a
3780    // page: a ceiling exists to bound how many rows travel, and answering
3781    // "how many are there" with "as many as I would have sent you" is not an
3782    // answer to the question. Hasura says so too -- a role limited to one row
3783    // of `article` still counts three of them and still gets `max(id)` over
3784    // all three, which is what `agg_perm` tests.
3785    //
3786    // The request's own `limit` and `offset` do still apply: those were asked
3787    // for, and `article_aggregate(limit: 2)` is a question about two rows. The
3788    // corpus proves this only for the permission's ceiling; the configured one
3789    // is treated the same way because the reason is the same, and because a
3790    // server whose `PGRST_MAX_ROWS` silently became the answer to `count`
3791    // would be wrong in the way that is hardest to notice.
3792    let mut counted = rows;
3793    if let Some(limit) = requested_limit {
3794        counted.push_str(&format!(" LIMIT {}", limit));
3795    }
3796    if let Some(offset) = offset {
3797        counted.push_str(&format!(" OFFSET {}", offset));
3798    }
3799
3800    // What the client actually asked for. Two selections of one aggregate are
3801    // one entry: `sum { id }` beside `totals: sum { views }` reads the same
3802    // object under two names, so the columns are the union of what both named
3803    // and the object is built once.
3804    let mut wants_nodes = false;
3805    let mut wants_aggregate = false;
3806    let mut wanted: BTreeMap<String, Vec<String>> = BTreeMap::new();
3807    // `count` is different: each occurrence may count something else, so each
3808    // is answered under the name it was asked for.
3809    let mut counts: Vec<(String, String)> = Vec::new();
3810    for selection in ctx.field().selection_set() {
3811        match selection.name() {
3812            "nodes" => wants_nodes = true,
3813            "aggregate" => {
3814                wants_aggregate = true;
3815                for function in selection.selection_set() {
3816                    // Answered by the schema, at this level and the one below.
3817                    if function.name() == "__typename" {
3818                        continue;
3819                    }
3820                    if function.name() == "count" {
3821                        counts.push((
3822                            function.alias().unwrap_or("count").to_string(),
3823                            count_expression(
3824                                function,
3825                                (&spec.schema_name, &spec.table_name),
3826                                None,
3827                                spec.names.as_ref(),
3828                            ),
3829                        ));
3830                        continue;
3831                    }
3832                    // Entered even when it names no column: `max { __typename }`
3833                    // is an object with nothing in it, not a null.
3834                    let columns = wanted.entry(function.name().to_string()).or_default();
3835                    for column in function.selection_set() {
3836                        let name = column.name().to_string();
3837                        if name != "__typename" && !columns.contains(&name) {
3838                            columns.push(name);
3839                        }
3840                    }
3841                }
3842            }
3843            _ => {}
3844        }
3845    }
3846
3847    let mut result = async_graphql::indexmap::IndexMap::new();
3848
3849    // Whatever `aggregate` names -- even if that is only `__typename` -- it is
3850    // an object, so it is answered rather than left null.
3851    if wants_aggregate {
3852        // `count` is answered whether or not it was asked for: the aggregate
3853        // type's own resolver reads a missing key as zero, and a client that
3854        // asked for nothing else still gets a number rather than a null.
3855        let mut parts = match counts.iter().any(|(key, _)| key == "count") {
3856            true => Vec::new(),
3857            false => vec!["'count', count(*)".to_string()],
3858        };
3859        for (key, expression) in &counts {
3860            parts.push(format!("'{}', {}", key.replace('\'', "''"), expression));
3861        }
3862        for (function, columns) in &wanted {
3863            // The function name comes from the schema, not from the request:
3864            // a selection can only name a field that was generated, so there
3865            // is no path from client text to this identifier.
3866            let sql_function = agg::NUMERIC_AGGREGATES
3867                .iter()
3868                .chain(agg::ORDERED_AGGREGATES.iter())
3869                .find(|(name, _)| name == function)
3870                .map(|(name, _)| *name)
3871                .ok_or_else(|| {
3872                    async_graphql::Error::new(format!("unknown aggregate \"{}\"", function))
3873                })?;
3874            let per_column: Vec<String> = columns
3875                .iter()
3876                .map(|field| {
3877                    let source = spec
3878                        .names
3879                        .column_source(&spec.schema_name, &spec.table_name, field)
3880                        .unwrap_or(field);
3881                    format!(
3882                        "'{}', {}({})",
3883                        field.replace('\'', "''"),
3884                        sql_function,
3885                        postrust_sql::escape_ident(source)
3886                    )
3887                })
3888                .collect();
3889            parts.push(format!(
3890                "'{}', json_build_object({})",
3891                sql_function,
3892                per_column.join(", ")
3893            ));
3894        }
3895
3896        let sql = format!(
3897            "SELECT json_build_object({}) FROM ({}) AS pgrst_agg",
3898            parts.join(", "),
3899            counted
3900        );
3901        let mut conn =
3902            begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3903        let rows = execute_query_on(&mut conn, &sql, &bound_values).await?;
3904        conn.commit().await?;
3905        if let Some(first) = rows.into_iter().next() {
3906            if let Value::Object(map) = json_to_value(first) {
3907                result.insert(async_graphql::Name::new("aggregate"), Value::Object(map));
3908            }
3909        }
3910    }
3911
3912    if wants_nodes {
3913        // `nodes` is the same rows selection the plain root field answers, so
3914        // it gets the same projection: computed fields, which are functions of
3915        // the row rather than part of it, and relationships, which are
3916        // correlated subselects. Reading it as columns alone was why a
3917        // relationship asked for beside a count came back null.
3918        let nodes = ctx
3919            .field()
3920            .selection_set()
3921            .find(|selection| selection.name() == "nodes")
3922            .ok_or_else(|| async_graphql::Error::new("nodes was asked for and then was not"))?;
3923
3924        // The aggregate query has already been sent, so its parameters are
3925        // fixed; anything an embed binds continues that numbering in a vector
3926        // of its own.
3927        let mut node_values = bound_values.clone();
3928        let (projection, row_column_types) = {
3929            let guard = gql_ctx
3930                .schema_cache
3931                .get()
3932                .await
3933                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3934            let cache = guard
3935                .as_ref()
3936                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3937            let qi = postrust_core::api_request::QualifiedIdentifier::new(
3938                &spec.schema_name,
3939                &spec.table_name,
3940            );
3941            let table = cache.get_table(&qi);
3942            let mut param_idx = node_values.len() + 1;
3943            let computed = match table {
3944                Some(table) => computed_projections(
3945                    table,
3946                    nodes.selection_set(),
3947                    "src",
3948                    spec.names.as_ref(),
3949                    cache,
3950                    &mut param_idx,
3951                    &mut node_values,
3952                )?,
3953                None => Vec::new(),
3954            };
3955            let embeds = build_embed_expressions(
3956                &gql_ctx.caller(),
3957                cache,
3958                spec.relationships.as_ref(),
3959                &spec.type_name,
3960                "src",
3961                nodes.selection_set(),
3962                spec.max_rows,
3963                &mut 0,
3964                &mut param_idx,
3965                &mut node_values,
3966                spec.names.as_ref(),
3967            )?;
3968            // A renamed column is renamed here, in the projection over the
3969            // subquery -- not inside it, where `src` has to stay the table's
3970            // own composite for a computed field to be passed the row.
3971            let mut projection = table
3972                .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
3973                .unwrap_or_else(|| "src.*".to_string());
3974            for expression in &computed {
3975                projection.push_str(", ");
3976                projection.push_str(expression);
3977            }
3978            for (field_name, expression) in &embeds {
3979                projection.push_str(", ");
3980                projection.push_str(expression);
3981                projection.push_str(" AS ");
3982                projection.push_str(&postrust_sql::escape_ident(field_name));
3983            }
3984            (
3985                projection,
3986                table
3987                    .map(|table| exposed_column_types(table, spec.names.as_ref()))
3988                    .unwrap_or_default(),
3989            )
3990        };
3991
3992        let sql = format!(
3993            "SELECT {} FROM (SELECT {} FROM ({}) AS src) AS pgrst_nodes",
3994            row_json("pgrst_nodes", &row_column_types),
3995            projection,
3996            inner
3997        );
3998        let mut conn =
3999            begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
4000        let rows = execute_query_on(&mut conn, &sql, &node_values).await?;
4001        conn.commit().await?;
4002        result.insert(
4003            async_graphql::Name::new("nodes"),
4004            Value::List(rows.into_iter().map(json_to_value).collect()),
4005        );
4006    }
4007
4008    let _ = &spec.type_name;
4009    Ok(Value::Object(result))
4010}
4011
4012/// Resolve a query field.
4013async fn resolve_query<'a>(
4014    ctx: &ResolverContext<'a>,
4015    spec: &QueryFieldSpec,
4016) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4017    Ok(rows_as_field_value(
4018        query_rows(ctx, spec).await?,
4019        spec.is_by_pk,
4020    ))
4021}
4022
4023/// The rows a query field answers with, as JSON.
4024///
4025/// Split from the field's own resolver because a subscription reads the same
4026/// rows over and over and has to tell one answer from the last: a
4027/// `FieldValue` is neither comparable nor readable, and this is.
4028async fn query_rows(
4029    ctx: &ResolverContext<'_>,
4030    spec: &QueryFieldSpec,
4031) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4032    let schema_name = spec.schema_name.as_str();
4033    let table_name = spec.table_name.as_str();
4034    let type_name = spec.type_name.as_str();
4035    let is_by_pk = spec.is_by_pk;
4036    let pk_columns = spec.pk_columns.as_slice();
4037    let max_rows = spec.max_rows;
4038    let relationships = spec.relationships.as_ref();
4039
4040    let pool = ctx.data::<PgPool>()?;
4041    let gql_ctx = ctx.data::<GraphQLContext>()?;
4042
4043    debug!("Resolving query for table: {}", table_name);
4044
4045    // Extract pagination arguments
4046    let requested_limit: Option<i64> = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
4047
4048    let offset: Option<i64> = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
4049
4050    // A query that names no limit would otherwise select the whole table, so
4051    // the configured ceiling is applied as the limit in that case, and as an
4052    // upper bound when the query asks for more than it. A by-PK query resolves
4053    // to at most one row.
4054    let limit: Option<i64> = if is_by_pk {
4055        Some(1)
4056    } else {
4057        // The permission's ceiling is one more of the same kind, so it folds
4058        // in the same way: whichever of the three is smallest wins, and a
4059        // request naming none takes the lowest that was named.
4060        let ceiling = match (
4061            max_rows,
4062            permission_limit(
4063                &gql_ctx.caller(),
4064                spec.names.as_ref(),
4065                schema_name,
4066                table_name,
4067            ),
4068        ) {
4069            (Some(configured), Some(granted)) => Some(configured.min(granted)),
4070            (only, None) | (None, only) => only,
4071        };
4072        match (requested_limit, ceiling) {
4073            (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
4074            (Some(requested), None) => Some(requested),
4075            (None, ceiling) => ceiling,
4076        }
4077    };
4078
4079    // Build the WHERE clause.
4080    //
4081    // A by-PK query filters on the table's key columns; each value is bound as
4082    // a parameter and cast to the column's type, since GraphQL scalars and
4083    // PostgreSQL types do not line up (a `uuid` key arrives as a String). A
4084    // list query filters on the `filter` argument, which takes the same shape
4085    // as a mutation's `where`.
4086    let mut where_sql = String::new();
4087    let mut bound_values: Vec<serde_json::Value> = Vec::new();
4088
4089    // Where the rows come from. A function's own arguments are bound before
4090    // anything else, because they sit in the FROM clause and every other
4091    // parameter is numbered after them.
4092    let source = match &spec.call {
4093        // Aliased rather than named. A whole-row reference -- what a computed
4094        // field is passed -- can only be written as a bare name, and
4095        // `"public"."author"` is not one: PostgreSQL reads it as a column of a
4096        // table called `public`. An alias no column can share is a name that
4097        // works in both positions.
4098        None => format!(
4099            "{}.{} AS {}",
4100            postrust_sql::escape_ident(schema_name),
4101            postrust_sql::escape_ident(table_name),
4102            postrust_sql::escape_ident(READ_ROW)
4103        ),
4104        Some(call) => function_source(ctx, call, table_name, &mut bound_values)?,
4105    };
4106
4107    // How a column of the source is referred to: a table by its qualified
4108    // name, a function's rows by the alias above.
4109    let source_ref = match &spec.call {
4110        None => postrust_sql::escape_ident(READ_ROW),
4111        Some(_) => postrust_sql::escape_ident(table_name),
4112    };
4113
4114    if is_by_pk {
4115        if pk_columns.is_empty() {
4116            return Err(async_graphql::Error::new(format!(
4117                "\"{}\" has no primary key, so it cannot be queried by key",
4118                table_name
4119            )));
4120        }
4121
4122        let mut conditions = Vec::with_capacity(pk_columns.len());
4123        for (idx, (col_name, pg_type)) in pk_columns.iter().enumerate() {
4124            let field = spec
4125                .names
4126                .column(schema_name, table_name, col_name)
4127                .unwrap_or(col_name);
4128            let value = ctx.args.try_get(field).map_err(|_| {
4129                async_graphql::Error::new(format!(
4130                    "missing required primary key argument \"{}\"",
4131                    field
4132                ))
4133            })?;
4134
4135            conditions.push(format!(
4136                "{} = ${}::{}",
4137                postrust_sql::escape_ident(col_name),
4138                idx + 1,
4139                pg_type
4140            ));
4141            bound_values.push(accessor_to_json(&value));
4142        }
4143        where_sql = format!(" WHERE {}", conditions.join(" AND "));
4144    }
4145
4146    // What this role may read, beside what the request asked for. Applied to
4147    // both shapes and to neither: a query naming no `where` at all is still a
4148    // read, and a by-key query that skipped the permission would be the one
4149    // way to fetch a row the filter withholds.
4150    {
4151        let requested = match is_by_pk {
4152            true => None,
4153            false => ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v)),
4154        };
4155        let permission = permission_predicate(
4156            &gql_ctx.caller(),
4157            spec.names.as_ref(),
4158            schema_name,
4159            table_name,
4160        )?;
4161
4162        if let Some(predicate) = and_predicates(requested, permission) {
4163            let guard = gql_ctx
4164                .schema_cache
4165                .get()
4166                .await
4167                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4168            let cache = guard
4169                .as_ref()
4170                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4171            let scope = match &spec.call {
4172                None => WhereScope::table(schema_name, table_name, type_name, spec.names.as_ref())
4173                    .under_alias(READ_ROW)
4174                    .with_resolution(cache, relationships)
4175                    .for_caller(gql_ctx.caller()),
4176                // A function's rows are the table's rows, so they are renamed
4177                // the same way; only how they are reached differs.
4178                Some(_) => WhereScope::for_alias(
4179                    schema_name,
4180                    table_name,
4181                    table_name,
4182                    type_name,
4183                    cache,
4184                    relationships,
4185                    spec.names.as_ref(),
4186                )
4187                .for_caller(gql_ctx.caller()),
4188            };
4189            let (filter_sql, filter_values) =
4190                build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
4191            if !filter_sql.is_empty() {
4192                // A by-key query already has a `WHERE`, so this joins it
4193                // rather than replacing it.
4194                where_sql = match where_sql.is_empty() {
4195                    true => format!(" {}", filter_sql),
4196                    false => format!(
4197                        "{} AND ({})",
4198                        where_sql,
4199                        filter_sql.trim_start_matches("WHERE ")
4200                    ),
4201                };
4202                bound_values.extend(filter_values);
4203            }
4204        }
4205    }
4206
4207    // A by-key query resolves to one row, so neither ordering nor distinct
4208    // has anything to do.
4209    let (order_sql, distinct_on) = if is_by_pk {
4210        (String::new(), Vec::new())
4211    } else {
4212        (
4213            build_order_by_clause(
4214                ctx,
4215                &gql_ctx.schema_cache,
4216                schema_name,
4217                table_name,
4218                type_name,
4219                relationships,
4220                &source_ref,
4221                spec.names.as_ref(),
4222            )
4223            .await?,
4224            build_distinct_on(
4225                ctx,
4226                &gql_ctx.schema_cache,
4227                schema_name,
4228                table_name,
4229                spec.names.as_ref(),
4230            )
4231            .await?,
4232        )
4233    };
4234
4235    // PostgreSQL keeps the first row of each DISTINCT ON group in the query's
4236    // own order, and picks arbitrarily where the ordering does not begin with
4237    // the distinct columns -- so which row survives would depend on the plan.
4238    // Hasura refuses that query rather than answering it, and so does this:
4239    // prepending the distinct columns instead produced an answer, and a wrong
4240    // one, since `ORDER BY "department", "department" DESC` is decided by its
4241    // first term and sorts ascending.
4242    let (distinct_sql, order_sql) = {
4243        let written = order_sql.strip_prefix(" ORDER BY ");
4244        // Reported against the field's arguments as a whole: the rule is about
4245        // two of them disagreeing, so neither one is where it went wrong.
4246        let (prefix, order) = distinct_on_clause(&distinct_on, written).map_err(|error| {
4247            at_path(
4248                error,
4249                &format!(
4250                    "$.selectionSet.{}.args",
4251                    ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4252                ),
4253            )
4254        })?;
4255        (
4256            prefix,
4257            match order {
4258                Some(order) => format!(" ORDER BY {}", order),
4259                None => String::new(),
4260            },
4261        )
4262    };
4263
4264    // A computed column is a function of the row rather than part of it, so
4265    // it is not in `*` and is named only when it was asked for.
4266    let (computed, row_column_types) = {
4267        let guard = gql_ctx
4268            .schema_cache
4269            .get()
4270            .await
4271            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4272        match guard.as_ref() {
4273            Some(cache) => {
4274                let qi =
4275                    postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
4276                match cache.get_table(&qi) {
4277                    Some(table) => {
4278                        let mut param_idx = bound_values.len() + 1;
4279                        (
4280                            computed_projections(
4281                                table,
4282                                ctx.field().selection_set(),
4283                                "src",
4284                                spec.names.as_ref(),
4285                                cache,
4286                                &mut param_idx,
4287                                &mut bound_values,
4288                            )?,
4289                            exposed_column_types(table, spec.names.as_ref()),
4290                        )
4291                    }
4292                    None => (Vec::new(), HashMap::new()),
4293                }
4294            }
4295            None => (Vec::new(), HashMap::new()),
4296        }
4297    };
4298    // ORDER BY, LIMIT and OFFSET belong inside the subquery: applying them to
4299    // the outer `row_to_json` projection would leave the ordering of the rows
4300    // that survive the limit unspecified.
4301    let mut inner = format!(
4302        "SELECT {}* FROM {}{}{}",
4303        distinct_sql, source, where_sql, order_sql
4304    );
4305
4306    if let Some(limit) = limit {
4307        inner.push_str(&format!(" LIMIT {}", limit));
4308    }
4309
4310    if let Some(offset) = offset {
4311        inner.push_str(&format!(" OFFSET {}", offset));
4312    }
4313
4314    // Embed the requested relationships in this same query, as correlated
4315    // subselects in the SELECT list, so the whole selection is one round trip.
4316    let embed_expressions = {
4317        let guard = gql_ctx
4318            .schema_cache
4319            .get()
4320            .await
4321            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4322        match guard.as_ref() {
4323            Some(cache) => {
4324                // Parameters bound inside an embed continue the outer query's
4325                // numbering: sqlx binds by position, so the values only have
4326                // to be pushed in the order their placeholders were handed
4327                // out.
4328                let mut param_idx = bound_values.len() + 1;
4329                build_embed_expressions(
4330                    &gql_ctx.caller(),
4331                    cache,
4332                    relationships,
4333                    type_name,
4334                    "src",
4335                    ctx.field().selection_set(),
4336                    max_rows,
4337                    &mut 0,
4338                    &mut param_idx,
4339                    &mut bound_values,
4340                    spec.names.as_ref(),
4341                )?
4342            }
4343            None => Vec::new(),
4344        }
4345    };
4346
4347    // A computed column is a function of the row, and the row it is a function
4348    // of is the table's -- not the subquery's. Projecting it inside the
4349    // subquery gave `src` an extra column, which is what made a computed
4350    // relationship beside it fail with `cannot cast type record to author`: a
4351    // subquery alias is only passable as a composite value while its columns
4352    // are exactly the table's. So both live out here, where `src` still is
4353    // one.
4354    let renamed = {
4355        let guard = gql_ctx
4356            .schema_cache
4357            .get()
4358            .await
4359            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4360        guard
4361            .as_ref()
4362            .and_then(|cache| {
4363                cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
4364                    schema_name,
4365                    table_name,
4366                ))
4367            })
4368            .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4369    };
4370    let inner = if embed_expressions.is_empty() && computed.is_empty() && renamed.is_none() {
4371        inner
4372    } else {
4373        let mut projection = renamed.unwrap_or_else(|| "src.*".to_string());
4374        for expression in &computed {
4375            projection.push_str(", ");
4376            projection.push_str(expression);
4377        }
4378        for (field_name, expression) in &embed_expressions {
4379            projection.push_str(", ");
4380            projection.push_str(expression);
4381            projection.push_str(" AS ");
4382            projection.push_str(&postrust_sql::escape_ident(field_name));
4383        }
4384        format!("SELECT {} FROM ({}) AS src", projection, inner)
4385    };
4386
4387    let sql = format!(
4388        "SELECT {} FROM ({}) t",
4389        row_json("t", &row_column_types),
4390        inner
4391    );
4392
4393    // One transaction for the query and any embeds hanging off it, so the role
4394    // applies to all of them and the parent and child rows come from a single
4395    // snapshot.
4396    let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
4397
4398    // Execute query - returns Vec<serde_json::Value>
4399    let mut result = execute_query_on(&mut tx, &sql, &bound_values).await?;
4400
4401    // Anything the single-query form did not cover.
4402    if embed_expressions.is_empty() {
4403        let guard = gql_ctx
4404            .schema_cache
4405            .get()
4406            .await
4407            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4408        let cache = guard
4409            .as_ref()
4410            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4411
4412        let embed_ctx = EmbedContext {
4413            schema_cache: cache,
4414            relationships,
4415            max_rows,
4416        };
4417
4418        embed_relationships(&mut tx, &embed_ctx, type_name, ctx.field(), &mut result).await?;
4419    }
4420
4421    tx.commit().await?;
4422
4423    Ok(result)
4424}
4425
4426/// The rows a field answers with, in the shape its type says.
4427///
4428/// A by-key field is one row or none; every other is a list, empty where
4429/// nothing matched rather than null.
4430fn rows_as_field_value<'a>(rows: Vec<serde_json::Value>, is_by_pk: bool) -> Option<FieldValue<'a>> {
4431    match is_by_pk {
4432        true => rows
4433            .into_iter()
4434            .next()
4435            .map(|v| FieldValue::value(json_to_value(v))),
4436        false => Some(FieldValue::list(
4437            rows.into_iter()
4438                .map(|v| FieldValue::value(json_to_value(v))),
4439        )),
4440    }
4441}
4442
4443/// Resolve a mutation field.
4444#[allow(clippy::too_many_arguments)]
4445async fn resolve_mutation<'a>(
4446    ctx: &ResolverContext<'a>,
4447    schema_name: &str,
4448    table_name: &str,
4449    mutation_type: MutationType,
4450    pk_columns: &[(String, String)],
4451    relationships: &Arc<HashMap<String, Vec<RelationshipField>>>,
4452    type_names: &Arc<HashMap<(String, String), String>>,
4453    names: &Arc<crate::names::NameOverrides>,
4454    max_rows: Option<i64>,
4455) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4456    let pool = ctx.data::<PgPool>()?;
4457    let gql_ctx = ctx.data::<GraphQLContext>()?;
4458
4459    debug!(
4460        "Resolving mutation for table: {} type: {:?}",
4461        table_name, mutation_type
4462    );
4463
4464    // A relationship or a computed field asked for beside the written columns
4465    // is not in `RETURNING`, so the rows are read again through the projection
4466    // an ordinary query uses -- and only when the selection asks for something
4467    // that needs it. A delete cannot do that afterwards and builds it into the
4468    // statement instead, which is why this is settled before the statement is.
4469    let type_name = type_names
4470        .get(&(schema_name.to_string(), table_name.to_string()))
4471        .cloned()
4472        .unwrap_or_else(|| table_name.to_string());
4473
4474    let by_key = matches!(
4475        mutation_type,
4476        MutationType::InsertOne | MutationType::UpdateByPk | MutationType::DeleteByPk
4477    );
4478    let returning = if by_key {
4479        Some(ctx.field())
4480    } else {
4481        ctx.field()
4482            .selection_set()
4483            .find(|selection| selection.name() == "returning")
4484    };
4485
4486    // Several updates, each with its own filter, applied in the order given.
4487    // They share the operation's transaction like any other write, which is
4488    // the whole difference from sending them one at a time: either all of them
4489    // happened or none did.
4490    if mutation_type == MutationType::UpdateMany {
4491        let updates = ctx
4492            .args
4493            .try_get("updates")
4494            .ok()
4495            .map(|v| accessor_to_json(&v))
4496            .unwrap_or(serde_json::Value::Null);
4497        let serde_json::Value::Array(entries) = updates else {
4498            return Err(async_graphql::Error::new(
4499                "\"updates\" takes a list of {where, _set, …} objects",
4500            ));
4501        };
4502        let column_types = column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await;
4503        let mut answers: Vec<FieldValue<'_>> = Vec::with_capacity(entries.len());
4504        for entry in entries {
4505            let serde_json::Value::Object(entry) = entry else {
4506                return Err(async_graphql::Error::new(
4507                    "each update is an object of {where, _set, …}",
4508                ));
4509            };
4510            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4511                .iter()
4512                .filter_map(|name| entry.get(*name).map(|value| (*name, value.clone())))
4513                .filter(|(_, value)| !value.is_null())
4514                .collect();
4515            let rows = execute_update(
4516                pool,
4517                gql_ctx,
4518                schema_name,
4519                table_name,
4520                &type_name,
4521                operators,
4522                column_types.clone(),
4523                entry.get("where").cloned(),
4524                relationships.as_ref(),
4525                names.as_ref(),
4526            )
4527            .await?;
4528            let affected = rows.len();
4529            let rows = match returning {
4530                Some(returning) if !rows.is_empty() => {
4531                    reread_returning(
4532                        pool,
4533                        gql_ctx,
4534                        schema_name,
4535                        table_name,
4536                        &type_name,
4537                        rows,
4538                        returning,
4539                        relationships.as_ref(),
4540                        names.as_ref(),
4541                        max_rows,
4542                    )
4543                    .await?
4544                }
4545                _ => rows,
4546            };
4547            answers.extend(mutation_result(rows, affected, false));
4548        }
4549        return Ok(Some(FieldValue::list(answers)));
4550    }
4551
4552    let (result, affected) = match mutation_type {
4553        MutationType::Insert | MutationType::InsertOne => {
4554            let objects = ctx
4555                .args
4556                .try_get("objects")
4557                .ok()
4558                .map(|v| accessor_to_json(&v))
4559                .or_else(|| {
4560                    // `insert_x_one(object: {...})` is one row spelled without
4561                    // the list.
4562                    ctx.args
4563                        .try_get("object")
4564                        .ok()
4565                        .map(|v| serde_json::Value::Array(vec![accessor_to_json(&v)]))
4566                })
4567                .unwrap_or_else(|| serde_json::Value::Array(vec![]));
4568
4569            let guard = gql_ctx
4570                .schema_cache
4571                .get()
4572                .await
4573                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4574            let cache = guard
4575                .as_ref()
4576                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4577            // Where this write is, in the document the client sent. The
4578            // field as it was written -- an alias if there was one, since that
4579            // is what names this selection.
4580            let here = format!(
4581                "$.selectionSet.{}",
4582                ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4583            );
4584            // `insert_x(objects: [...])` against `insert_x_one(object: {...})`:
4585            // the same write under two spellings, and the path says which.
4586            let named = match mutation_type {
4587                MutationType::InsertOne => "object",
4588                _ => "objects",
4589            };
4590            let context = InsertContext {
4591                on_conflict: ctx
4592                    .args
4593                    .try_get("on_conflict")
4594                    .ok()
4595                    .map(|v| accessor_to_json(&v))
4596                    .filter(|v| !v.is_null()),
4597                cache,
4598                relationships: relationships.as_ref(),
4599                type_names: type_names.as_ref(),
4600                names: names.as_ref(),
4601                caller: gql_ctx.caller(),
4602                // Filled in per row by `execute_insert`, which is where the
4603                // index is known.
4604                row: String::new(),
4605                objects: format!("{}.args.{}", here, named),
4606                conflict: format!("{}.args.on_conflict", here),
4607            };
4608
4609            execute_insert(
4610                pool,
4611                gql_ctx,
4612                schema_name,
4613                table_name,
4614                objects,
4615                &context,
4616                mutation_type == MutationType::InsertOne,
4617            )
4618            .await?
4619        }
4620        // `UpdateMany` answered above: it is a list of responses rather than
4621        // one, so it does not share this shape.
4622        MutationType::UpdateMany | MutationType::Update | MutationType::UpdateByPk => {
4623            // `_set` replaces, the others read the column they write. A client
4624            // may send more than one, so all of them are collected rather than
4625            // the first that happens to be present.
4626            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4627                .iter()
4628                .filter_map(|name| {
4629                    ctx.args
4630                        .try_get(name)
4631                        .ok()
4632                        .map(|v| (*name, accessor_to_json(&v)))
4633                })
4634                .filter(|(_, value)| !value.is_null())
4635                .collect();
4636
4637            let where_clause = if mutation_type == MutationType::UpdateByPk {
4638                Some(pk_where_from_args(
4639                    ctx,
4640                    schema_name,
4641                    table_name,
4642                    pk_columns,
4643                    names.as_ref(),
4644                )?)
4645            } else {
4646                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4647            };
4648            // Which rows this role may change, beside which rows the request
4649            // asked to change. A by-key update is narrowed too: naming the key
4650            // of a row the permission withholds must not be a way to reach it.
4651            let where_clause = and_predicates(
4652                where_clause,
4653                permission_filter(
4654                    &gql_ctx.caller(),
4655                    names.as_ref(),
4656                    schema_name,
4657                    table_name,
4658                    crate::role::Verb::Update,
4659                )?,
4660            );
4661
4662            execute_update(
4663                pool,
4664                gql_ctx,
4665                schema_name,
4666                table_name,
4667                &type_name,
4668                operators,
4669                column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await,
4670                where_clause,
4671                relationships.as_ref(),
4672                names.as_ref(),
4673            )
4674            .await
4675            .map(|rows| {
4676                let count = rows.len();
4677                (rows, count)
4678            })?
4679        }
4680        MutationType::Delete | MutationType::DeleteByPk => {
4681            let where_clause = if mutation_type == MutationType::DeleteByPk {
4682                Some(pk_where_from_args(
4683                    ctx,
4684                    schema_name,
4685                    table_name,
4686                    pk_columns,
4687                    names.as_ref(),
4688                )?)
4689            } else {
4690                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4691            };
4692            let where_clause = and_predicates(
4693                where_clause,
4694                permission_filter(
4695                    &gql_ctx.caller(),
4696                    names.as_ref(),
4697                    schema_name,
4698                    table_name,
4699                    crate::role::Verb::Delete,
4700                )?,
4701            );
4702
4703            execute_delete(
4704                pool,
4705                gql_ctx,
4706                schema_name,
4707                table_name,
4708                &type_name,
4709                where_clause,
4710                returning,
4711                relationships.as_ref(),
4712                names.as_ref(),
4713                max_rows,
4714            )
4715            .await
4716            .map(|rows| {
4717                let count = rows.len();
4718                (rows, count)
4719            })?
4720        }
4721    };
4722
4723    let is_delete = matches!(
4724        mutation_type,
4725        MutationType::Delete | MutationType::DeleteByPk
4726    );
4727    let result = match returning {
4728        Some(returning) if !result.is_empty() && !is_delete => {
4729            reread_returning(
4730                pool,
4731                gql_ctx,
4732                schema_name,
4733                table_name,
4734                &type_name,
4735                result,
4736                returning,
4737                relationships.as_ref(),
4738                names.as_ref(),
4739                max_rows,
4740            )
4741            .await?
4742        }
4743        _ => result,
4744    };
4745
4746    Ok(mutation_result(result, affected, by_key))
4747}
4748
4749/// The transaction this operation's writes share, opening it if this is the
4750/// first one.
4751///
4752/// Held for the length of the write, and settled by whoever answers the
4753/// request -- see [`crate::context::SharedWrite`]. Mutation root fields are
4754/// resolved one after another, so the lock is never contended; it is what
4755/// makes the transaction reachable from a resolver that owns nothing.
4756async fn write_tx<'a>(
4757    gql_ctx: &'a GraphQLContext,
4758    pool: &PgPool,
4759) -> Result<
4760    tokio::sync::MutexGuard<'a, Option<sqlx::Transaction<'static, sqlx::Postgres>>>,
4761    async_graphql::Error,
4762> {
4763    let mut guard = gql_ctx.write.lock().await;
4764    if guard.is_none() {
4765        *guard = Some(begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?);
4766    }
4767    Ok(guard)
4768}
4769
4770/// Begin a transaction with the request's role and session variables applied.
4771///
4772/// The settings go in before the role does. A role with fewer privileges may
4773/// not be allowed to call `set_config` at all, and a policy that reads a
4774/// setting the caller could then change is not a policy.
4775async fn begin_with_session(
4776    pool: &PgPool,
4777    role: &str,
4778    settings: &[(String, String)],
4779) -> Result<sqlx::Transaction<'static, sqlx::Postgres>, async_graphql::Error> {
4780    let mut tx = pool.begin().await?;
4781
4782    // One statement for all of them, which is what the REST path does. A loop
4783    // here costs a round trip per session variable, and there is always at
4784    // least one: `hasura.session` and `hasura.role` are set on every request,
4785    // so even an anonymous read paid two. An authenticated caller with six
4786    // `x-hasura-*` variables paid eight.
4787    //
4788    // The values stay bound rather than interpolated. `set_config` takes both
4789    // as arguments, where `SET LOCAL` would need the value written into the
4790    // statement text.
4791    if !settings.is_empty() {
4792        let calls = (0..settings.len())
4793            .map(|i| format!("set_config(${}, ${}, true)", i * 2 + 1, i * 2 + 2))
4794            .collect::<Vec<_>>()
4795            .join(", ");
4796        let sql = format!("SELECT {calls}");
4797        let mut query = sqlx::query_scalar::<_, String>(&sql);
4798        for (name, value) in settings {
4799            query = query.bind(name).bind(value);
4800        }
4801        query.fetch_optional(&mut *tx).await?;
4802    }
4803
4804    sqlx::query(&format!(
4805        "SET LOCAL ROLE {}",
4806        postrust_sql::escape_ident(role)
4807    ))
4808    .execute(&mut *tx)
4809    .await?;
4810    Ok(tx)
4811}
4812
4813/// Render a row as JSON, with any shape column rendered as GeoJSON.
4814///
4815/// PostgreSQL's text form for a geometry is WKB hex --
4816/// `0103000020E6100000…` -- which is what `row_to_json` puts in the response
4817/// and is not what any client can read. Hasura answers GeoJSON, and a client
4818/// migrating from it parses GeoJSON.
4819///
4820/// The shape columns are merged over the row rather than replacing the
4821/// projection, so a table with no shape column produces exactly the SQL it did
4822/// before and one with three needs no list of the others.
4823fn row_json(expr: &str, column_types: &HashMap<String, String>) -> String {
4824    let mut shapes: Vec<(&String, &String)> = column_types
4825        .iter()
4826        .filter(|(_, pg_type)| matches!(pg_type.as_str(), "geometry" | "geography"))
4827        .collect();
4828    if shapes.is_empty() {
4829        return format!("row_to_json({})", expr);
4830    }
4831    shapes.sort();
4832
4833    let overrides: Vec<String> = shapes
4834        .iter()
4835        .map(|(name, _)| {
4836            format!(
4837                // Option 4 asks PostGIS for the long-form CRS member --
4838                // `urn:ogc:def:crs:EPSG::4326` -- which Hasura includes and
4839                // which a client round-tripping a shape needs in order to
4840                // write it back where it came from. PostGIS emits it only
4841                // where the geometry has an SRID to report.
4842                "'{}', ST_AsGeoJSON({}.{}, 9, 4)::jsonb",
4843                name.replace('\'', "''"),
4844                expr,
4845                postrust_sql::escape_ident(name)
4846            )
4847        })
4848        .collect();
4849    format!(
4850        "(to_jsonb({}) || jsonb_build_object({}))",
4851        expr,
4852        overrides.join(", ")
4853    )
4854}
4855
4856/// Execute a SQL query and return results as serde_json::Value.
4857/// We keep data as serde_json::Value so field resolvers can use try_downcast_ref.
4858async fn execute_query_on(
4859    conn: &mut sqlx::PgConnection,
4860    sql: &str,
4861    params: &[serde_json::Value],
4862) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4863    use sqlx::Row;
4864
4865    trace!("Executing SQL: {}", sql);
4866
4867    // Execute query
4868    let mut query = sqlx::query(sql);
4869    for param in params {
4870        query = bind_json_value(query, param);
4871    }
4872    let rows = query.fetch_all(&mut *conn).await.map_err(database_error)?;
4873
4874    // Return raw JSON values - don't convert to async_graphql::Value
4875    // This allows field resolvers to use try_downcast_ref::<serde_json::Value>()
4876    let results: Vec<serde_json::Value> = rows
4877        .into_iter()
4878        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
4879        .collect();
4880
4881    Ok(results)
4882}
4883
4884/// Refuse a GeoJSON document PostGIS would accept and should not.
4885///
4886/// `ST_GeomFromGeoJSON` will build a "Polygon" from three points that do not
4887/// meet, and a "LineString" from one point -- neither of which is a shape, and
4888/// both of which are then stored and read back as though they were. Hasura
4889/// checks the document before it reaches the database, refuses with
4890/// `parse-failed`, and says which rule was broken; the messages here are its
4891/// messages, because a client that reports them to a user is reporting text it
4892/// already ships.
4893///
4894/// A value that is not an object is left alone: a string in a geometry column
4895/// is WKT or WKB hex, which is a different thing entirely.
4896fn check_geojson(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4897    fn refuse<T>(message: &str) -> Result<T, async_graphql::Error> {
4898        Err(coded_error("parse-failed", message))
4899    }
4900
4901    fn position(
4902        value: &serde_json::Value,
4903    ) -> Result<&Vec<serde_json::Value>, async_graphql::Error> {
4904        match value.as_array() {
4905            Some(items) if items.len() >= 2 && items.iter().all(|i| i.is_number()) => Ok(items),
4906            _ => refuse("A Position needs at least 2 elements"),
4907        }
4908    }
4909
4910    fn positions(
4911        value: Option<&serde_json::Value>,
4912        least: usize,
4913        what: &str,
4914    ) -> Result<Vec<Vec<serde_json::Value>>, async_graphql::Error> {
4915        let items = match value.and_then(|v| v.as_array()) {
4916            Some(items) => items,
4917            None => return refuse(&format!("A {} needs at least {} Positions", what, least)),
4918        };
4919        if items.len() < least {
4920            return refuse(&format!("A {} needs at least {} Positions", what, least));
4921        }
4922        items.iter().map(|item| position(item).cloned()).collect()
4923    }
4924
4925    fn ring(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4926        let points = positions(Some(value), 4, "LinearRing")?;
4927        match (points.first(), points.last()) {
4928            (Some(first), Some(last)) if first == last => Ok(()),
4929            _ => refuse("the first and last locations have to be equal for a LinearRing"),
4930        }
4931    }
4932
4933    fn polygon(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4934        let Some(rings) = value.as_array() else {
4935            return refuse("A LinearRing needs at least 4 Positions");
4936        };
4937        rings.iter().try_for_each(ring)
4938    }
4939
4940    let serde_json::Value::Object(map) = value else {
4941        return Ok(());
4942    };
4943    let Some(kind) = map.get("type").and_then(|v| v.as_str()) else {
4944        return Ok(());
4945    };
4946    let coordinates = map.get("coordinates");
4947    match kind {
4948        "Point" => {
4949            position(coordinates.unwrap_or(&serde_json::Value::Null))?;
4950        }
4951        "MultiPoint" => {
4952            positions(coordinates, 0, "MultiPoint")?;
4953        }
4954        "LineString" => {
4955            positions(coordinates, 2, "LineString")?;
4956        }
4957        "MultiLineString" => {
4958            for line in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4959                positions(Some(line), 2, "LineString")?;
4960            }
4961        }
4962        "Polygon" => polygon(coordinates.unwrap_or(&serde_json::Value::Null))?,
4963        "MultiPolygon" => {
4964            for one in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4965                polygon(one)?;
4966            }
4967        }
4968        "GeometryCollection" => {
4969            for geometry in map
4970                .get("geometries")
4971                .and_then(|v| v.as_array())
4972                .into_iter()
4973                .flatten()
4974            {
4975                check_geojson(geometry)?;
4976            }
4977        }
4978        other => return refuse(&format!("unexpected geometry type: {}", other)),
4979    }
4980    Ok(())
4981}
4982
4983/// The call that produces a computed field's value.
4984///
4985/// Two spellings of the same row, because the two calls need different ones.
4986/// A positional call takes `alias.*`, which is what a qualified table can be
4987/// written as; a named call takes the row as one value, which only a bare
4988/// alias is. Named notation is needed exactly where a session argument is,
4989/// since the row is then not the only parameter and may not be the first.
4990fn computed_call(
4991    definition: &postrust_core::schema_cache::ComputedColumn,
4992    row: &str,
4993    row_by_name: &str,
4994) -> String {
4995    let function = format!(
4996        "{}.{}",
4997        postrust_sql::escape_ident(&definition.function.schema),
4998        postrust_sql::escape_ident(&definition.function.name)
4999    );
5000    match (&definition.session_argument, &definition.row_argument) {
5001        (Some(session), Some(row_argument)) => format!(
5002            "{}({} => {}, {} => {})",
5003            function,
5004            postrust_sql::escape_ident(row_argument),
5005            row_by_name,
5006            postrust_sql::escape_ident(session),
5007            SESSION_ARGUMENT
5008        ),
5009        _ => format!("{}({})", function, row),
5010    }
5011}
5012
5013/// What a `hasura_session` argument is given.
5014///
5015/// The session document, read from the setting `begin_with_session` writes.
5016/// `current_setting`'s second argument makes a missing setting null rather than
5017/// an error, which is the case of a request with no session at all.
5018const SESSION_ARGUMENT: &str = "coalesce(current_setting('hasura.session', true), '{}')::json";
5019
5020/// Everything an update may be told to do, in the order the schema declares
5021/// them. Read by both spellings: one update, and one of many.
5022const UPDATE_OPERATORS: [&str; 7] = [
5023    "_set",
5024    "_inc",
5025    "_append",
5026    "_prepend",
5027    "_delete_key",
5028    "_delete_elem",
5029    "_delete_at_path",
5030];
5031
5032/// What a read table is called inside its own query.
5033///
5034/// See [`WRITTEN_ROW`]: the row a query passes to a computed field has to be
5035/// named, and a table's qualified name is not a name a row can be written
5036/// under.
5037const READ_ROW: &str = "pgrst_src";
5038
5039/// What a written table is called inside its own statement.
5040///
5041/// See [`WhereScope::under_alias`]: the row a statement returns has to be
5042/// named, and a table's own name is read as a column first.
5043const WRITTEN_ROW: &str = "pgrst_row";
5044
5045/// The cast a written value needs to reach a column of this type.
5046///
5047/// A bound parameter arrives as text. PostgreSQL will coerce it to a numeric
5048/// or a date on assignment, but not to `jsonb`, an array, or a user-defined
5049/// type -- `column "details" is of type jsonb but expression is of type text`
5050/// is what an uncast insert answers. Naming the column's own type covers all
5051/// of them and changes nothing where the coercion would have worked anyway.
5052fn write_cast(column_types: &HashMap<String, String>, column: &str) -> String {
5053    match column_types.get(column) {
5054        Some(pg_type) => format!("::{}", pg_type),
5055        None => String::new(),
5056    }
5057}
5058
5059/// The value one column is written with, as it goes over the wire.
5060///
5061/// A `json` column holds a JSON value, and `"[]"` is one -- a string whose
5062/// characters happen to be brackets. Binding the bare text and casting stored
5063/// an empty array instead, so a client that wrote a string read back a list.
5064/// The document a column is given is the document it keeps.
5065fn write_operand(
5066    column_types: &HashMap<String, String>,
5067    column: &str,
5068    value: &serde_json::Value,
5069) -> serde_json::Value {
5070    match column_types.get(column).map(String::as_str) {
5071        Some("json") | Some("jsonb") => match value {
5072            serde_json::Value::String(_)
5073            | serde_json::Value::Number(_)
5074            | serde_json::Value::Bool(_) => serde_json::Value::String(value.to_string()),
5075            other => other.clone(),
5076        },
5077        // An array column takes an array literal. `["a","b"]` is JSON, and
5078        // PostgreSQL reads a text parameter destined for a `text[]` as
5079        // `{a,b}` -- `malformed array literal` is what it says about the
5080        // other spelling.
5081        Some(pg_type) if is_array_type(pg_type) => match value {
5082            serde_json::Value::Array(_) => serde_json::Value::String(array_literal(value)),
5083            other => other.clone(),
5084        },
5085        _ => value.clone(),
5086    }
5087}
5088
5089/// Whether a PostgreSQL type name is an array.
5090///
5091/// Both spellings appear in the catalogue depending on how it was asked:
5092/// `text[]` from `format_type`, `_text` from `typname`.
5093fn is_array_type(pg_type: &str) -> bool {
5094    pg_type.ends_with("[]") || pg_type.starts_with('_')
5095}
5096
5097/// A JSON array as PostgreSQL writes one.
5098///
5099/// `["a","b"]` becomes `{"a","b"}`. Every element is quoted, so a value
5100/// containing a comma, a brace or a backslash survives; a null is the unquoted
5101/// `NULL`, which is the only way to write one.
5102fn array_literal(value: &serde_json::Value) -> String {
5103    fn element(value: &serde_json::Value) -> String {
5104        match value {
5105            serde_json::Value::Null => "NULL".to_string(),
5106            serde_json::Value::Array(_) => array_literal(value),
5107            serde_json::Value::String(text) => {
5108                format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
5109            }
5110            other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
5111        }
5112    }
5113    let items = match value {
5114        serde_json::Value::Array(items) => items,
5115        other => return element(other),
5116    };
5117    format!(
5118        "{{{}}}",
5119        items.iter().map(element).collect::<Vec<_>>().join(",")
5120    )
5121}
5122
5123/// The expression that writes one bound value into one column.
5124///
5125/// A cast is enough for almost everything. A shape is the exception: a client
5126/// sends GeoJSON, which is what Hasura accepts, and `'{"type":"Point",…}'` is
5127/// not something PostgreSQL will cast to a geometry -- it has a function for
5128/// reading that, and the function is the only way in.
5129///
5130/// Only when the value actually is an object. A string in a geometry column is
5131/// WKT or WKB hex, which the cast does read, and passing that to
5132/// `ST_GeomFromGeoJSON` would refuse a perfectly good value.
5133fn write_expression(
5134    column_types: &HashMap<String, String>,
5135    column: &str,
5136    value: &serde_json::Value,
5137    placeholder: &str,
5138) -> String {
5139    let pg_type = column_types.get(column).map(String::as_str);
5140    if value.is_object() && matches!(pg_type, Some("geometry") | Some("geography")) {
5141        return match pg_type {
5142            Some("geography") => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
5143            _ => format!("ST_GeomFromGeoJSON({})", placeholder),
5144        };
5145    }
5146    // An amount written as a number is reached through one: PostgreSQL has no
5147    // cast from `double precision` to `money`, which is what a bound JSON
5148    // number arrives as, and `numeric` is the type it does take one from. One
5149    // written as text -- `"$12,344.57"` -- is read by `money` directly, and
5150    // going through `numeric` would refuse the currency symbol.
5151    if pg_type == Some("money") && value.is_number() {
5152        return format!("{}::numeric::money", placeholder);
5153    }
5154    format!("{}{}", placeholder, write_cast(column_types, column))
5155}
5156
5157/// Every column of a table with the type it is declared as.
5158async fn column_types_of(
5159    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
5160    schema_name: &str,
5161    table_name: &str,
5162) -> HashMap<String, String> {
5163    let Ok(guard) = schema_cache.get().await else {
5164        return HashMap::new();
5165    };
5166    let Some(cache) = guard.as_ref() else {
5167        return HashMap::new();
5168    };
5169    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5170    match cache.get_table(&qi) {
5171        Some(table) => table
5172            .columns
5173            .values()
5174            .map(|c| (c.name.clone(), c.nominal_type.clone()))
5175            .collect(),
5176        None => HashMap::new(),
5177    }
5178}
5179
5180/// Insert one row, and any rows nested inside it.
5181///
5182/// Hasura writes a parent and its children in one mutation:
5183///
5184/// ```graphql
5185/// insert_article(objects: [{title: "x", author: {data: {name: "y"}}}])
5186/// ```
5187///
5188/// Which row goes first follows from which side holds the key. A relationship
5189/// to one row is reached through a column of *this* table, so the related row
5190/// is inserted first and its key is what this row's column is set to. A
5191/// relationship to many rows is reached through a column of *theirs*, so this
5192/// row goes first and each child's column is set from it.
5193///
5194/// Everything runs in the transaction the caller opened, so a child that
5195/// cannot be written takes the parent with it -- which is what writing them in
5196/// one mutation means.
5197fn insert_row<'life>(
5198    conn: &'life mut sqlx::PgConnection,
5199    schema_name: &'life str,
5200    table_name: &'life str,
5201    object: serde_json::Map<String, serde_json::Value>,
5202    context: &'life InsertContext<'life>,
5203    written_count: &'life mut usize,
5204) -> std::pin::Pin<
5205    Box<
5206        dyn std::future::Future<Output = Result<serde_json::Value, async_graphql::Error>>
5207            + Send
5208            + 'life,
5209    >,
5210> {
5211    Box::pin(async move {
5212        use sqlx::Row;
5213
5214        let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5215        let table = context.cache.get_table(&qi).ok_or_else(|| {
5216            async_graphql::Error::new(format!("unknown table \"{}\"", table_name))
5217        })?;
5218        let column_types: HashMap<String, String> = table
5219            .columns
5220            .values()
5221            .map(|c| (c.name.clone(), c.nominal_type.clone()))
5222            .collect();
5223
5224        // A key that is a relationship rather than a column is a nested write.
5225        let type_name = context
5226            .type_names
5227            .get(&(schema_name.to_string(), table_name.to_string()));
5228        let relationships: &[RelationshipField] = type_name
5229            .and_then(|name| context.relationships.get(name))
5230            .map(|r| r.as_slice())
5231            .unwrap_or(&[]);
5232
5233        let mut columns = serde_json::Map::new();
5234        type Nested<'r> = (
5235            &'r RelationshipField,
5236            serde_json::Value,
5237            Option<serde_json::Value>,
5238        );
5239        let mut to_one: Vec<Nested> = Vec::new();
5240        let mut to_many: Vec<Nested> = Vec::new();
5241
5242        for (key, value) in object {
5243            match relationships.iter().find(|r| r.name == key) {
5244                None => {
5245                    // A written value is keyed by the field it was sent under;
5246                    // the statement is written in the table's own columns.
5247                    columns.insert(
5248                        table_column_for(context.names, table, &key).to_string(),
5249                        value,
5250                    );
5251                }
5252                Some(_) if value.is_null() => {
5253                    // `author: null` is a row with no author, not a row whose
5254                    // author is a null object. A client writing one insert for
5255                    // both cases sends the relationship either way.
5256                    continue;
5257                }
5258                Some(relationship) => {
5259                    // `{data: {...}}` for one row, `{data: [{...}]}` for many,
5260                    // and an `on_conflict` beside either -- a nested row is
5261                    // upserted the same way a top-level one is.
5262                    let (data, conflict) = match &value {
5263                        serde_json::Value::Object(map) => (
5264                            map.get("data").cloned().unwrap_or(value.clone()),
5265                            map.get("on_conflict").cloned().filter(|v| !v.is_null()),
5266                        ),
5267                        other => (other.clone(), None),
5268                    };
5269                    // Which row goes first follows from which side holds the
5270                    // key, not from how many rows there are. A one-to-one
5271                    // whose child key *is* the parent's -- `author_detail.id`
5272                    // referencing `author.id` -- is one row either way, and
5273                    // the parent still has to be written before there is a key
5274                    // to give it.
5275                    if child_holds_the_key(&relationship.relationship) {
5276                        to_many.push((relationship, data, conflict));
5277                    } else {
5278                        to_one.push((relationship, data, conflict));
5279                    }
5280                }
5281            }
5282        }
5283
5284        // A shape is checked before it is written, since PostGIS would build
5285        // one from a document that is not a shape.
5286        for (column, value) in &columns {
5287            if matches!(
5288                column_types.get(column).map(String::as_str),
5289                Some("geometry") | Some("geography")
5290            ) {
5291                // Under the name the client wrote it under, which is the
5292                // exposed one rather than the column's where they differ.
5293                check_geojson(value).map_err(|error| {
5294                    let field = context
5295                        .names
5296                        .column(schema_name, table_name, column)
5297                        .unwrap_or(column);
5298                    at_path(error, &format!("{}.{}", context.row, field))
5299                })?;
5300            }
5301        }
5302
5303        // The rows this one points at, first: this row's own column carries
5304        // their key.
5305        for (relationship, data, conflict) in to_one {
5306            let plan =
5307                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5308                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5309            let serde_json::Value::Object(child) = data else {
5310                return Err(async_graphql::Error::new(format!(
5311                    "\"{}\" takes an object to insert",
5312                    relationship.name
5313                )));
5314            };
5315            // Writing the related row is what fills this row's key column in,
5316            // so a value already written there is a second answer to the same
5317            // question and one of them would be silently discarded.
5318            for (local, _) in &plan.columns {
5319                if columns.get(local).is_some_and(|v| !v.is_null()) {
5320                    return Err(at_path(
5321                        validation_error(format!(
5322                            "cannot insert object relationship \"{}\" as \"{}\" column \
5323                             values are already determined",
5324                            relationship.name, local
5325                        )),
5326                        &format!("{}.{}", context.row, relationship.name),
5327                    ));
5328                }
5329            }
5330            // A to-one row is written under the relationship's own name:
5331            // `...objects[0].author.data`, and its `on_conflict` beside it.
5332            let under = format!("{}.{}", context.row, relationship.name);
5333            let nested = InsertContext {
5334                on_conflict: conflict,
5335                cache: context.cache,
5336                relationships: context.relationships,
5337                type_names: context.type_names,
5338                names: context.names,
5339                caller: context.caller,
5340                row: format!("{}.data", under),
5341                objects: format!("{}.data", under),
5342                conflict: format!("{}.on_conflict", under),
5343            };
5344            let written = insert_row(
5345                conn,
5346                &plan.foreign_schema,
5347                &plan.foreign_table,
5348                child,
5349                &nested,
5350                written_count,
5351            )
5352            .await?;
5353            for (local, foreign) in &plan.columns {
5354                if let Some(value) = written.get(foreign) {
5355                    columns.insert(local.clone(), value.clone());
5356                }
5357            }
5358        }
5359
5360        // What the server fills in regardless of what was sent. Applied after
5361        // the nested rows have contributed their keys and before the columns
5362        // are counted, because a preset is a column this statement writes --
5363        // and applied *over* the request, since a preset the client could
5364        // override is not a preset.
5365        for (column, value) in crate::role::presets(
5366            &context.caller,
5367            context.names,
5368            schema_name,
5369            table_name,
5370            crate::role::Verb::Insert,
5371        )
5372        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?
5373        {
5374            columns.insert(column, value);
5375        }
5376
5377        // `ON CONFLICT` says which uniqueness is being resolved against and
5378        // what to do about it. An empty `update_columns` is `DO NOTHING`,
5379        // which is how a client says "leave the row that is already there".
5380        // The columns this statement writes, settled before the conflict
5381        // clause so a predicate on it can number its parameters after theirs.
5382        let column_names: Vec<&str> = columns.keys().map(|k| k.as_str()).collect();
5383        let mut conflict_values: Vec<serde_json::Value> = Vec::new();
5384        let conflict_sql = match &context.on_conflict {
5385            Some(serde_json::Value::Object(spec)) => {
5386                let constraint = spec.get("constraint").and_then(|v| v.as_str());
5387                let Some(constraint) = constraint else {
5388                    return Err(async_graphql::Error::new(
5389                        "on_conflict needs a constraint to resolve against",
5390                    ));
5391                };
5392                if !table
5393                    .unique_constraints
5394                    .iter()
5395                    .any(|(name, _)| name == constraint)
5396                {
5397                    return Err(async_graphql::Error::new(format!(
5398                        "\"{}\" is not a unique constraint of \"{}\"",
5399                        constraint, table_name
5400                    )));
5401                }
5402                let updates: Vec<&str> = spec
5403                    .get("update_columns")
5404                    .and_then(|v| v.as_array())
5405                    .map(|items| items.iter().filter_map(|i| i.as_str()).collect())
5406                    .unwrap_or_default();
5407                // The member that stands for the columns this role has none
5408                // of. It is in the enum so that `on_conflict` exists at all;
5409                // naming it is the one thing it cannot be used for.
5410                if updates.contains(&PLACEHOLDER_COLUMN) {
5411                    return Err(at_path(
5412                        coded_error("validation-failed", "erroneous column name"),
5413                        &context.conflict,
5414                    ));
5415                }
5416
5417                if updates.is_empty() {
5418                    format!(
5419                        " ON CONFLICT ON CONSTRAINT {} DO NOTHING",
5420                        postrust_sql::escape_ident(constraint)
5421                    )
5422                } else {
5423                    // An `ON CONFLICT DO UPDATE` is an update, so the update
5424                    // permission's presets are written here too -- and a
5425                    // column a preset writes does not also take its value from
5426                    // `EXCLUDED`, which is what "a preset overrides the
5427                    // request" means on this side.
5428                    let preset = crate::role::presets(
5429                        &context.caller,
5430                        context.names,
5431                        schema_name,
5432                        table_name,
5433                        crate::role::Verb::Update,
5434                    )
5435                    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5436
5437                    // Numbered after the row's own values and before the
5438                    // predicate below, because `SET` is written before `WHERE`
5439                    // and the parameters are bound in the order they appear.
5440                    let mut param_idx = column_names.len() + 1;
5441                    let mut assignments: Vec<String> =
5442                        Vec::with_capacity(preset.len() + updates.len());
5443                    for (column, value) in &preset {
5444                        assignments.push(format!(
5445                            "{} = {}",
5446                            postrust_sql::escape_ident(column),
5447                            write_expression(
5448                                &column_types,
5449                                column,
5450                                value,
5451                                &format!("${}", param_idx)
5452                            )
5453                        ));
5454                        conflict_values.push(write_operand(&column_types, column, value));
5455                        param_idx += 1;
5456                    }
5457                    for field in &updates {
5458                        let column = table_column_for(context.names, table, field);
5459                        if preset.iter().any(|(written, _)| written == column) {
5460                            continue;
5461                        }
5462                        assignments.push(format!(
5463                            "{} = EXCLUDED.{}",
5464                            postrust_sql::escape_ident(column),
5465                            postrust_sql::escape_ident(column)
5466                        ));
5467                    }
5468                    // `where` on an upsert decides whether the row that is
5469                    // already there is overwritten -- "only if what I am
5470                    // writing is newer". It reads the existing row, which is
5471                    // what the statement's alias names.
5472                    //
5473                    // The update permission's own filter is ANDed into it: an
5474                    // upsert that overwrites a row is an update, and a role
5475                    // that may not update a row may not reach it by inserting
5476                    // over it. Compiling that filter is also where a session
5477                    // variable it names and the caller does not carry is
5478                    // noticed -- which is the answer Hasura gives before it
5479                    // writes anything.
5480                    let granted = permission_filter(
5481                        &context.caller,
5482                        context.names,
5483                        schema_name,
5484                        table_name,
5485                        crate::role::Verb::Update,
5486                    )?;
5487                    let requested = spec.get("where").filter(|f| !f.is_null()).cloned();
5488                    let condition = match and_predicates(requested, granted) {
5489                        Some(filter) => {
5490                            let filter = &filter;
5491                            let type_name = context
5492                                .type_names
5493                                .get(&(schema_name.to_string(), table_name.to_string()))
5494                                .map(String::as_str)
5495                                .unwrap_or(table_name);
5496                            let scope = WhereScope::table(
5497                                schema_name,
5498                                table_name,
5499                                type_name,
5500                                context.names,
5501                            )
5502                            .under_alias(WRITTEN_ROW)
5503                            .with_resolution(context.cache, context.relationships);
5504                            let mut alias_counter = 0usize;
5505                            build_condition(
5506                                filter,
5507                                &scope,
5508                                &mut param_idx,
5509                                &mut conflict_values,
5510                                &mut alias_counter,
5511                            )?
5512                            .map(|sql| format!(" WHERE {}", sql))
5513                            .unwrap_or_default()
5514                        }
5515                        None => String::new(),
5516                    };
5517                    format!(
5518                        " ON CONFLICT ON CONSTRAINT {} DO UPDATE SET {}{}",
5519                        postrust_sql::escape_ident(constraint),
5520                        assignments.join(", "),
5521                        condition
5522                    )
5523                }
5524            }
5525            _ => String::new(),
5526        };
5527
5528        // The row this statement writes, as `RETURNING` names it. Not the
5529        // table's own name: qualifying it reads as a column of a table called
5530        // `public`, which is what `missing FROM-clause entry for table
5531        // "public"` means, and leaving it bare reads as a column of the table
5532        // where one shares the name.
5533        let qualified_table = postrust_sql::escape_ident(WRITTEN_ROW);
5534
5535        // What the written row has to satisfy for the write to stand,
5536        // evaluated in `RETURNING` against the row as written. Its parameters
5537        // are numbered after the values and the conflict clause, since
5538        // `RETURNING` is last.
5539        let check_scope = WhereScope::for_alias(
5540            schema_name,
5541            table_name,
5542            WRITTEN_ROW,
5543            context
5544                .type_names
5545                .get(&(schema_name.to_string(), table_name.to_string()))
5546                .map(String::as_str)
5547                .unwrap_or(table_name),
5548            context.cache,
5549            context.relationships,
5550            context.names,
5551        );
5552        let (check_sql, check_values) = {
5553            let check = crate::role::write_check(
5554                &context.caller,
5555                context.names,
5556                schema_name,
5557                table_name,
5558                crate::role::Verb::Insert,
5559            )
5560            .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5561            match check {
5562                None => (String::new(), Vec::new()),
5563                Some(check) => {
5564                    let mut values = Vec::new();
5565                    let mut param_idx = column_names.len() + conflict_values.len() + 1;
5566                    let mut alias_counter = 0usize;
5567                    match build_condition(
5568                        &check,
5569                        &check_scope,
5570                        &mut param_idx,
5571                        &mut values,
5572                        &mut alias_counter,
5573                    )? {
5574                        None => (String::new(), Vec::new()),
5575                        Some(sql) => (
5576                            format!(
5577                                ", ({}) AS {}",
5578                                sql,
5579                                postrust_sql::escape_ident(CHECK_COLUMN)
5580                            ),
5581                            values,
5582                        ),
5583                    }
5584                }
5585            }
5586        };
5587
5588        let names = column_names;
5589        let written = if names.is_empty() {
5590            // Every column defaulted. `DEFAULT VALUES` is how SQL says that;
5591            // an empty column list is a syntax error.
5592            let sql = format!(
5593                "INSERT INTO {}.{} AS {} DEFAULT VALUES{} RETURNING {}{}",
5594                postrust_sql::escape_ident(schema_name),
5595                postrust_sql::escape_ident(table_name),
5596                qualified_table,
5597                conflict_sql,
5598                row_json(&qualified_table, &column_types),
5599                check_sql
5600            );
5601            let mut query = sqlx::query(&sql);
5602            for value in &conflict_values {
5603                query = bind_json_value(query, value);
5604            }
5605            for value in &check_values {
5606                query = bind_json_value(query, value);
5607            }
5608            query
5609                .fetch_optional(&mut *conn)
5610                .await
5611                // A constraint the write violated is reported against the rows
5612                // the statement was given, which is where Hasura reports it.
5613                .map_err(|error| at_path(database_error(error), &context.objects))?
5614        } else {
5615            let placeholders: Vec<String> = names
5616                .iter()
5617                .enumerate()
5618                .map(|(i, column)| {
5619                    let placeholder = format!("${}", i + 1);
5620                    match columns.get(*column) {
5621                        Some(value) => write_expression(&column_types, column, value, &placeholder),
5622                        None => placeholder,
5623                    }
5624                })
5625                .collect();
5626            let sql = format!(
5627                "INSERT INTO {}.{} AS {} ({}) VALUES ({}){} RETURNING {}{}",
5628                postrust_sql::escape_ident(schema_name),
5629                postrust_sql::escape_ident(table_name),
5630                qualified_table,
5631                names
5632                    .iter()
5633                    .map(|c| postrust_sql::escape_ident(c))
5634                    .collect::<Vec<_>>()
5635                    .join(", "),
5636                placeholders.join(", "),
5637                conflict_sql,
5638                row_json(&qualified_table, &column_types),
5639                check_sql
5640            );
5641            trace!("Executing INSERT SQL: {}", sql);
5642            let mut query = sqlx::query(&sql);
5643            for column in &names {
5644                if let Some(value) = columns.get(*column) {
5645                    query = bind_json_value(query, &write_operand(&column_types, column, value));
5646                }
5647            }
5648            for value in &conflict_values {
5649                query = bind_json_value(query, value);
5650            }
5651            for value in &check_values {
5652                query = bind_json_value(query, value);
5653            }
5654            query
5655                .fetch_optional(&mut *conn)
5656                .await
5657                // A constraint the write violated is reported against the rows
5658                // the statement was given, which is where Hasura reports it.
5659                .map_err(|error| at_path(database_error(error), &context.objects))?
5660        };
5661
5662        // `DO NOTHING` writes nothing and returns nothing, which is the answer
5663        // rather than an error: the row that was already there stays.
5664        let Some(written) = written else {
5665            return Ok(serde_json::Value::Null);
5666        };
5667        refuse_unchecked_rows(std::slice::from_ref(&written), !check_sql.is_empty())
5668            .map_err(|error| at_path(error, &context.objects))?;
5669        let row: serde_json::Value = written.try_get(0).unwrap_or(serde_json::Value::Null);
5670        *written_count += 1;
5671
5672        // Then the rows that point at this one, which need its key.
5673        for (relationship, data, conflict) in to_many {
5674            let plan =
5675                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5676                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5677            let children = match data {
5678                serde_json::Value::Array(items) => items,
5679                serde_json::Value::Object(map) => vec![serde_json::Value::Object(map)],
5680                serde_json::Value::Null => Vec::new(),
5681                _ => {
5682                    return Err(async_graphql::Error::new(format!(
5683                        "\"{}\" takes objects to insert",
5684                        relationship.name
5685                    )))
5686                }
5687            };
5688            for (child_index, child) in children.into_iter().enumerate() {
5689                let serde_json::Value::Object(mut child) = child else {
5690                    continue;
5691                };
5692                for (local, foreign) in &plan.columns {
5693                    // The parent's key is what makes the child a child, so a
5694                    // child that writes it too is asking for a different
5695                    // parent than the one it is nested inside.
5696                    if child.get(foreign).is_some_and(|v| !v.is_null()) {
5697                        return Err(at_path(
5698                            validation_error(format!(
5699                                "cannot insert \"{}\" columns as their values are already \
5700                                 being determined by parent insert",
5701                                foreign
5702                            )),
5703                            &format!(
5704                                "{}.{}{}",
5705                                context.row,
5706                                relationship.name,
5707                                match relationship.is_list {
5708                                    true => format!(".data[{}]", child_index),
5709                                    false => ".data".to_string(),
5710                                }
5711                            ),
5712                        ));
5713                    }
5714                    if let Some(value) = row.get(local) {
5715                        child.insert(foreign.clone(), value.clone());
5716                    }
5717                }
5718                // `...objects[0].articles.data[0]`, which is the path
5719                // Hasura answers for a child that writes its parent's key.
5720                // One row written after its parent -- a mapped object
5721                // relationship with `after_parent` -- has no index: there is
5722                // no list for it to be an item of.
5723                let at = match relationship.is_list {
5724                    true => format!(".data[{}]", child_index),
5725                    false => ".data".to_string(),
5726                };
5727                let under = format!("{}.{}", context.row, relationship.name);
5728                let nested = InsertContext {
5729                    on_conflict: conflict.clone(),
5730                    cache: context.cache,
5731                    relationships: context.relationships,
5732                    type_names: context.type_names,
5733                    names: context.names,
5734                    caller: context.caller,
5735                    row: format!("{}{}", under, at),
5736                    objects: format!("{}.data", under),
5737                    conflict: format!("{}.on_conflict", under),
5738                };
5739                insert_row(
5740                    conn,
5741                    &plan.foreign_schema,
5742                    &plan.foreign_table,
5743                    child,
5744                    &nested,
5745                    written_count,
5746                )
5747                .await?;
5748            }
5749        }
5750
5751        Ok(row)
5752    })
5753}
5754
5755/// What a nested insert needs to follow a relationship.
5756///
5757/// Cloned per row and per nested row, which costs three strings: the paths are
5758/// the only part that differs between them, and the rest is references and a
5759/// `Caller`.
5760#[derive(Clone)]
5761struct InsertContext<'a> {
5762    /// What to do when this row conflicts. A nested row carries its own, from
5763    /// the `on_conflict` beside its `data`.
5764    on_conflict: Option<serde_json::Value>,
5765    cache: &'a SchemaCache,
5766    relationships: &'a HashMap<String, Vec<RelationshipField>>,
5767    /// (schema, table) -> the GraphQL type name, which keys the relationship
5768    /// map. A table's type is not always its name: a second schema prefixes
5769    /// it, and a name may have been given.
5770    type_names: &'a HashMap<(String, String), String>,
5771    /// The names columns are exposed under, so a written value keyed by a
5772    /// field reaches the column it names.
5773    names: &'a crate::names::NameOverrides,
5774    /// Who is writing, so that a permission's presets and its check reach
5775    /// every row this insert writes -- including the nested ones, which are
5776    /// rows of other tables under other rules.
5777    caller: crate::role::Caller<'a>,
5778    /// Where this row is in the request, as Hasura writes such a place:
5779    /// `$.selectionSet.insert_author.args.objects[0]`, and one level down
5780    /// `$.selectionSet.insert_author.args.objects[0].articles.data[0]`. A
5781    /// column of it is that with the field's name appended.
5782    row: String,
5783    /// Where the rows this statement writes were named. What a refused `check`
5784    /// is reported against -- the argument, not the row inside it, which is
5785    /// what Hasura answers.
5786    objects: String,
5787    /// Where this statement's `on_conflict` was named.
5788    conflict: String,
5789}
5790
5791/// Re-read written rows so that a mutation's `returning` can carry
5792/// relationships and computed fields.
5793///
5794/// `RETURNING row_to_json(t.*)` gives the table's own columns and nothing
5795/// else, so a relationship asked for beside them had no value to resolve --
5796/// and a non-null list field with no value is an error, which meant a mutation
5797/// that had written its rows correctly answered as though it had failed. The
5798/// write is not in doubt; only the shape of the answer is.
5799///
5800/// So the rows are read again by their primary key, through the same
5801/// projection an ordinary query uses. One extra round trip, and only when the
5802/// selection actually asks for something `RETURNING` cannot give.
5803#[allow(clippy::too_many_arguments)]
5804async fn reread_returning(
5805    pool: &PgPool,
5806    gql_ctx: &GraphQLContext,
5807    schema_name: &str,
5808    table_name: &str,
5809    type_name: &str,
5810    rows: Vec<Value>,
5811    returning: async_graphql::SelectionField<'_>,
5812    relationships: &HashMap<String, Vec<RelationshipField>>,
5813    names: &crate::names::NameOverrides,
5814    max_rows: Option<i64>,
5815) -> Result<Vec<Value>, async_graphql::Error> {
5816    let guard = gql_ctx
5817        .schema_cache
5818        .get()
5819        .await
5820        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5821    let Some(cache) = guard.as_ref() else {
5822        return Ok(rows);
5823    };
5824    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5825    let Some(table) = cache.get_table(&qi) else {
5826        return Ok(rows);
5827    };
5828    if table.pk_cols.is_empty() {
5829        // Nothing to identify the rows by. The columns are still right.
5830        return Ok(rows);
5831    }
5832
5833    let mut param_idx = 1usize;
5834    let mut values: Vec<serde_json::Value> = Vec::new();
5835    let mut alias_counter = 0usize;
5836    let embeds = build_embed_expressions(
5837        &gql_ctx.caller(),
5838        cache,
5839        relationships,
5840        type_name,
5841        "src",
5842        returning.selection_set(),
5843        max_rows,
5844        &mut alias_counter,
5845        &mut param_idx,
5846        &mut values,
5847        names,
5848    )?;
5849    let computed = computed_projections(
5850        table,
5851        returning.selection_set(),
5852        "src",
5853        names,
5854        cache,
5855        &mut param_idx,
5856        &mut values,
5857    )?;
5858    // A rename is reason enough to read the rows again: `RETURNING` answers in
5859    // the table's own column names, which are not the names the client asked
5860    // under.
5861    let renames = names.renames_columns(&table.schema, &table.name);
5862    if embeds.is_empty() && computed.is_empty() && !renames {
5863        return Ok(rows);
5864    }
5865
5866    // One row per key written, in the order they were written.
5867    let mut conditions = Vec::with_capacity(rows.len());
5868    for row in &rows {
5869        let mut parts = Vec::with_capacity(table.pk_cols.len());
5870        for column in &table.pk_cols {
5871            let Value::Object(map) = row else {
5872                return Ok(rows);
5873            };
5874            let Some(value) = map.get(&async_graphql::Name::new(column)) else {
5875                return Ok(rows);
5876            };
5877            parts.push(format!(
5878                "{} = ${}",
5879                postrust_sql::escape_ident(column),
5880                param_idx
5881            ));
5882            values.push(value_to_json(value));
5883            param_idx += 1;
5884        }
5885        conditions.push(format!("({})", parts.join(" AND ")));
5886    }
5887    if conditions.is_empty() {
5888        return Ok(rows);
5889    }
5890
5891    let mut projection =
5892        rename_projection(table, "src", names).unwrap_or_else(|| "src.*".to_string());
5893    for expression in &computed {
5894        projection.push_str(", ");
5895        projection.push_str(expression);
5896    }
5897    for (name, expression) in &embeds {
5898        projection.push_str(", ");
5899        projection.push_str(expression);
5900        projection.push_str(" AS ");
5901        projection.push_str(&postrust_sql::escape_ident(name));
5902    }
5903
5904    let sql = format!(
5905        "SELECT {} FROM (SELECT {} FROM (SELECT * FROM {}.{} WHERE {}) AS src) AS pgrst_r",
5906        row_json("pgrst_r", &exposed_column_types(table, names)),
5907        projection,
5908        postrust_sql::escape_ident(schema_name),
5909        postrust_sql::escape_ident(table_name),
5910        conditions.join(" OR ")
5911    );
5912
5913    // The same transaction the write went into: the rows it is reading are the
5914    // rows that write produced, and nothing has committed yet.
5915    let mut guard = write_tx(gql_ctx, pool).await?;
5916    let conn = guard.as_mut().expect("write_tx opens one");
5917    let reread = execute_query_on(conn, &sql, &values).await?;
5918    drop(guard);
5919
5920    if reread.is_empty() {
5921        return Ok(rows);
5922    }
5923    Ok(reread.into_iter().map(json_to_value).collect())
5924}
5925
5926/// Execute an insert mutation.
5927async fn execute_insert(
5928    pool: &PgPool,
5929    gql_ctx: &GraphQLContext,
5930    schema_name: &str,
5931    table_name: &str,
5932    objects: serde_json::Value,
5933    context: &InsertContext<'_>,
5934    // Whether the client wrote one row rather than a list of them. Only the
5935    // error paths care: `insert_x_one(object: {...})` has no row to index, so
5936    // its rows are named `args.object` and not `args.object[0]`.
5937    single: bool,
5938) -> Result<(Vec<Value>, usize), async_graphql::Error> {
5939    trace!("Insert mutation for {}: {:?}", table_name, objects);
5940
5941    // Handle both array and single object
5942    let objects_array = match objects {
5943        serde_json::Value::Array(arr) => arr,
5944        serde_json::Value::Object(obj) => vec![serde_json::Value::Object(obj)],
5945        _ => {
5946            return Err(async_graphql::Error::new(
5947                "objects must be an array or object",
5948            ))
5949        }
5950    };
5951
5952    if objects_array.is_empty() {
5953        return Err(async_graphql::Error::new("objects cannot be empty"));
5954    }
5955
5956    let mut guard = write_tx(gql_ctx, pool).await?;
5957    let conn = guard.as_mut().expect("write_tx opens one");
5958
5959    let mut inserted: Vec<Value> = Vec::new();
5960    let mut written = 0usize;
5961
5962    for (index, object) in objects_array.into_iter().enumerate() {
5963        let serde_json::Value::Object(map) = object else {
5964            return Err(async_graphql::Error::new(
5965                "each object to insert is an object",
5966            ));
5967        };
5968        // This row's own place in the request, which is what an error inside
5969        // it is reported against.
5970        let context = InsertContext {
5971            row: match single {
5972                true => context.objects.clone(),
5973                false => format!("{}[{}]", context.objects, index),
5974            },
5975            ..context.clone()
5976        };
5977        let row = insert_row(conn, schema_name, table_name, map, &context, &mut written).await?;
5978        // A row `DO NOTHING` left alone is not in `returning` and is not in
5979        // `affected_rows` either: nothing was written, and the row that was
5980        // already there is not this mutation's to report.
5981        if !row.is_null() {
5982            inserted.push(json_to_value(row));
5983        }
5984    }
5985
5986    Ok((inserted, written))
5987}
5988
5989/// Shape a mutation's result for the field that asked for it.
5990///
5991/// A by-key mutation answers with the row, or with null where the key matched
5992/// nothing. Every other mutation answers with the table's mutation response,
5993/// which carries `affected_rows` alongside the rows themselves -- a client may
5994/// ask for no rows back and still need the count, which is the usual case for
5995/// a delete.
5996///
5997/// The count is passed in rather than taken from the rows, because a nested
5998/// insert writes more rows than it returns: two articles each carrying a new
5999/// author is four rows written and two returned, and four is the answer.
6000fn mutation_result<'a>(rows: Vec<Value>, affected: usize, by_key: bool) -> Option<FieldValue<'a>> {
6001    if by_key {
6002        return rows.into_iter().next().map(FieldValue::value);
6003    }
6004    let mut response = async_graphql::indexmap::IndexMap::new();
6005    response.insert(
6006        async_graphql::Name::new("affected_rows"),
6007        Value::from(affected),
6008    );
6009    response.insert(async_graphql::Name::new("returning"), Value::List(rows));
6010    Some(FieldValue::value(Value::Object(response)))
6011}
6012
6013/// Bind a JSON value to a sqlx query.
6014fn bind_json_value<'q>(
6015    query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
6016    value: &serde_json::Value,
6017) -> sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments> {
6018    match value {
6019        serde_json::Value::Null => query.bind(None::<String>),
6020        serde_json::Value::Bool(b) => query.bind(*b),
6021        serde_json::Value::Number(n) => {
6022            if let Some(i) = n.as_i64() {
6023                query.bind(i)
6024            } else if let Some(f) = n.as_f64() {
6025                query.bind(f)
6026            } else {
6027                query.bind(n.to_string())
6028            }
6029        }
6030        serde_json::Value::String(s) => query.bind(s.clone()),
6031        _ => query.bind(value.to_string()),
6032    }
6033}
6034
6035/// Execute an update mutation.
6036#[allow(clippy::too_many_arguments)] // the predicate needs the schema it reads
6037async fn execute_update(
6038    pool: &PgPool,
6039    gql_ctx: &GraphQLContext,
6040    schema_name: &str,
6041    table_name: &str,
6042    type_name: &str,
6043    operators: Vec<(&'static str, serde_json::Value)>,
6044    column_types: HashMap<String, String>,
6045    where_clause: Option<serde_json::Value>,
6046    relationships: &HashMap<String, Vec<RelationshipField>>,
6047    names: &crate::names::NameOverrides,
6048) -> Result<Vec<Value>, async_graphql::Error> {
6049    use sqlx::Row;
6050
6051    trace!("Update mutation for {}: {:?}", table_name, operators);
6052
6053    // Build the SET clause. Each operator writes a column in terms of itself
6054    // except `_set`, which replaces it -- `_inc` adds, the jsonb operators
6055    // concatenate or remove, and a client may send several in one mutation as
6056    // long as they name different columns.
6057    let mut set_parts: Vec<String> = Vec::new();
6058    let mut set_values: Vec<serde_json::Value> = Vec::new();
6059    let mut param_idx = 1;
6060    let mut written: HashSet<String> = HashSet::new();
6061
6062    // What the permission writes whatever the request said. A preset overrides
6063    // rather than collides -- one a client could override is not a preset --
6064    // so these go in first and the operators below step around the columns
6065    // they name.
6066    //
6067    // This is also what makes an update with no `_set` at all a real update:
6068    // `update_resident(where: {...})` under a permission whose `set` names
6069    // `city` changes `city`, and answers with the rows it changed rather than
6070    // with nothing.
6071    let preset = crate::role::presets(
6072        &gql_ctx.caller(),
6073        names,
6074        schema_name,
6075        table_name,
6076        crate::role::Verb::Update,
6077    )
6078    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6079    let preset_columns: HashSet<&str> = preset.iter().map(|(name, _)| name.as_str()).collect();
6080    for (column, value) in &preset {
6081        let placeholder =
6082            write_expression(&column_types, column, value, &format!("${}", param_idx));
6083        set_parts.push(format!(
6084            "{} = {}",
6085            postrust_sql::escape_ident(column),
6086            placeholder
6087        ));
6088        set_values.push(write_operand(&column_types, column, value));
6089        param_idx += 1;
6090    }
6091
6092    for (operator, payload) in &operators {
6093        let serde_json::Value::Object(map) = payload else {
6094            return Err(async_graphql::Error::new(format!(
6095                "\"{}\" takes an object mapping columns to values",
6096                operator
6097            )));
6098        };
6099        for (field, value) in map {
6100            let column = &names
6101                .column_source(schema_name, table_name, field)
6102                .unwrap_or(field)
6103                .to_string();
6104            // A preset has this column. Not the duplicate-write error below:
6105            // the request is allowed to name it, and the permission wins.
6106            if preset_columns.contains(column.as_str()) {
6107                continue;
6108            }
6109            if !written.insert(column.clone()) {
6110                return Err(async_graphql::Error::new(format!(
6111                    "\"{}\" is written twice in one update; a column may be \
6112                     changed by one operator at a time",
6113                    column
6114                )));
6115            }
6116            let quoted = postrust_sql::escape_ident(column);
6117            // `_set` writes the column and `_inc` adds to it, so both take a
6118            // value of the column's type and are cast to it -- `money + $1`
6119            // is not an operator PostgreSQL has, and neither is `money +
6120            // double precision`, which is what a bound number arrives as. The
6121            // others already say what they need -- `::jsonb` for a
6122            // concatenation, `::text[]` for a path -- and casting twice would
6123            // be wrong for `_delete_elem`, whose operand is an integer rather
6124            // than a value of the column.
6125            let placeholder = match *operator {
6126                "_set" | "_inc" => {
6127                    write_expression(&column_types, column, value, &format!("${}", param_idx))
6128                }
6129                _ => format!("${}", param_idx),
6130            };
6131            // The assignment PostgreSQL needs, which for everything but `_set`
6132            // reads the column's current value.
6133            let assignment = match *operator {
6134                "_set" => format!("{} = {}", quoted, placeholder),
6135                "_inc" => format!("{} = {} + {}", quoted, quoted, placeholder),
6136                "_append" => format!("{} = {} || {}::jsonb", quoted, quoted, placeholder),
6137                "_prepend" => format!("{} = {}::jsonb || {}", quoted, placeholder, quoted),
6138                "_delete_key" => format!("{} = {} - {}::text", quoted, quoted, placeholder),
6139                "_delete_elem" => format!("{} = {} - {}::int", quoted, quoted, placeholder),
6140                // A path is a list of keys, and one parameter carrying
6141                // `["name","last"]` is a JSON array -- which PostgreSQL reads
6142                // as an array literal and refuses, `malformed array literal`.
6143                // The elements are bound one at a time into an `ARRAY[...]`,
6144                // the same way the key-existence comparisons take theirs.
6145                "_delete_at_path" => {
6146                    let Some(steps) = value.as_array() else {
6147                        return Err(async_graphql::Error::new(format!(
6148                            "\"_delete_at_path\" on \"{}\" takes a list of keys",
6149                            column
6150                        )));
6151                    };
6152                    let path = sql_array(steps, "text[]", &mut param_idx, &mut set_values);
6153                    set_parts.push(format!("{} = {} #- {}", quoted, quoted, path));
6154                    continue;
6155                }
6156                other => {
6157                    return Err(async_graphql::Error::new(format!(
6158                        "unsupported update operator \"{}\"",
6159                        other
6160                    )))
6161                }
6162            };
6163            if *operator == "_set"
6164                && matches!(
6165                    column_types.get(column).map(String::as_str),
6166                    Some("geometry") | Some("geography")
6167                )
6168            {
6169                check_geojson(value)?;
6170            }
6171            set_parts.push(assignment);
6172            set_values.push(match *operator {
6173                "_set" => write_operand(&column_types, column, value),
6174                _ => value.clone(),
6175            });
6176            param_idx += 1;
6177        }
6178    }
6179
6180    // An update that changes nothing changes no rows. Refusing it looks like
6181    // the stricter answer and is the wrong one: a client building `_set` from
6182    // a form the user submitted unchanged sends an empty object, and the
6183    // honest report of what happened is that nothing did.
6184    if set_parts.is_empty() {
6185        return Ok(Vec::new());
6186    }
6187
6188    // Build WHERE clause. It can follow a relationship, the same way a query's
6189    // can: `update_article(where: {author: {name: {_eq: "x"}}})` names the
6190    // rows by something they point at rather than by a column of their own.
6191    let guard = gql_ctx
6192        .schema_cache
6193        .get()
6194        .await
6195        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6196    let cache = guard
6197        .as_ref()
6198        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6199    let scope = WhereScope::table(schema_name, table_name, type_name, names)
6200        .under_alias(WRITTEN_ROW)
6201        .with_resolution(cache, relationships);
6202    let (where_sql, where_values) = build_where_clause(where_clause.as_ref(), param_idx, &scope)?;
6203
6204    // An absent or unrecognised `where` argument yields an empty clause, which
6205    // would update every row in the table. Refuse instead.
6206    if where_sql.is_empty() {
6207        return Err(async_graphql::Error::new(format!(
6208            "update on \"{}\" requires a `where` argument with at least one \
6209             recognised condition; refusing to update every row",
6210            table_name
6211        )));
6212    }
6213
6214    // What the changed row has to satisfy for the change to stand. Evaluated
6215    // in the `RETURNING` clause, against the row as written -- which is the
6216    // only place it can honestly be evaluated, and costs neither a re-read nor
6217    // a way to identify the rows afterwards.
6218    let (check_sql, check_values) = permission_check_sql(
6219        gql_ctx,
6220        names,
6221        schema_name,
6222        table_name,
6223        crate::role::Verb::Update,
6224        &scope,
6225        param_idx + where_values.len(),
6226    )?;
6227
6228    let sql = format!(
6229        "UPDATE {}.{} AS {} SET {} {} RETURNING {}{}",
6230        postrust_sql::escape_ident(schema_name),
6231        postrust_sql::escape_ident(table_name),
6232        postrust_sql::escape_ident(WRITTEN_ROW),
6233        set_parts.join(", "),
6234        where_sql,
6235        row_json(&postrust_sql::escape_ident(WRITTEN_ROW), &column_types),
6236        check_sql
6237    );
6238
6239    trace!("Executing UPDATE SQL: {}", sql);
6240
6241    // Build query with parameters
6242    let mut query = sqlx::query(&sql);
6243
6244    // Bind SET values
6245    for val in &set_values {
6246        query = bind_json_value(query, val);
6247    }
6248
6249    // Bind WHERE values
6250    for val in &where_values {
6251        query = bind_json_value(query, val);
6252    }
6253
6254    // And the check's, last, because `RETURNING` is.
6255    for val in &check_values {
6256        query = bind_json_value(query, val);
6257    }
6258
6259    let mut guard = write_tx(gql_ctx, pool).await?;
6260    let conn = guard.as_mut().expect("write_tx opens one");
6261    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6262
6263    refuse_unchecked_rows(&rows, !check_sql.is_empty())?;
6264
6265    let updated: Vec<Value> = rows
6266        .iter()
6267        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6268        .map(json_to_value)
6269        .collect();
6270
6271    Ok(updated)
6272}
6273
6274/// A permission's check, as a column of the write's `RETURNING`.
6275///
6276/// Empty where nothing has to be checked. Otherwise `, (<predicate>) AS ...`,
6277/// evaluated per written row against the row itself -- so a row the permission
6278/// refuses is known before the transaction is settled, and settling it is
6279/// already a rollback once an error is reported.
6280fn permission_check_sql(
6281    gql_ctx: &GraphQLContext,
6282    names: &crate::names::NameOverrides,
6283    schema_name: &str,
6284    table_name: &str,
6285    verb: crate::role::Verb,
6286    scope: &WhereScope<'_>,
6287    start_param_idx: usize,
6288) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6289    let check = crate::role::write_check(&gql_ctx.caller(), names, schema_name, table_name, verb)
6290        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6291    let Some(check) = check else {
6292        return Ok((String::new(), Vec::new()));
6293    };
6294
6295    let mut values = Vec::new();
6296    let mut param_idx = start_param_idx;
6297    let mut alias_counter = 0usize;
6298    let condition = build_condition(
6299        &check,
6300        scope,
6301        &mut param_idx,
6302        &mut values,
6303        &mut alias_counter,
6304    )?;
6305    Ok(match condition {
6306        // A check that compiles to nothing restricts nothing, which is what an
6307        // empty one says.
6308        None => (String::new(), Vec::new()),
6309        Some(sql) => (
6310            format!(
6311                ", ({}) AS {}",
6312                sql,
6313                postrust_sql::escape_ident(CHECK_COLUMN)
6314            ),
6315            values,
6316        ),
6317    })
6318}
6319
6320/// The column a permission's check is returned in.
6321const CHECK_COLUMN: &str = "pgrst_permission_check";
6322
6323/// Refuse the whole write if any row it produced fails the permission's check.
6324///
6325/// Every row, not the first: a mutation is one transaction and a single
6326/// refused row takes the rest with it, which is what makes a check a rule
6327/// about the write rather than about a row. A null counts as a failure -- an
6328/// unknown is not a satisfied condition.
6329fn refuse_unchecked_rows(
6330    rows: &[sqlx::postgres::PgRow],
6331    checked: bool,
6332) -> Result<(), async_graphql::Error> {
6333    if !checked {
6334        return Ok(());
6335    }
6336    use sqlx::Row;
6337    let all_passed = rows
6338        .iter()
6339        .all(|row| row.try_get::<bool, _>(CHECK_COLUMN).unwrap_or(false));
6340    match all_passed {
6341        true => Ok(()),
6342        false => Err(coded_error("permission-error", crate::role::CHECK_FAILED)),
6343    }
6344}
6345
6346/// Execute a delete mutation.
6347#[allow(clippy::too_many_arguments)] // the projection needs the schema it reads
6348async fn execute_delete(
6349    pool: &PgPool,
6350    gql_ctx: &GraphQLContext,
6351    schema_name: &str,
6352    table_name: &str,
6353    type_name: &str,
6354    where_clause: Option<serde_json::Value>,
6355    returning: Option<async_graphql::SelectionField<'_>>,
6356    relationships: &HashMap<String, Vec<RelationshipField>>,
6357    names: &crate::names::NameOverrides,
6358    max_rows: Option<i64>,
6359) -> Result<Vec<Value>, async_graphql::Error> {
6360    use sqlx::Row;
6361
6362    trace!("Delete mutation for {}", table_name);
6363
6364    let guard = gql_ctx
6365        .schema_cache
6366        .get()
6367        .await
6368        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6369    let cache = guard
6370        .as_ref()
6371        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6372    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
6373    let column_types = cache
6374        .get_table(&qi)
6375        .map(|table| exposed_column_types(table, names))
6376        .unwrap_or_default();
6377
6378    // Build WHERE clause. It can follow a relationship -- `delete_article(where:
6379    // {author: {id: {_eq: 2}}})` is the article-by-author delete anyone writes
6380    // -- so the scope is given the schema to resolve one with.
6381    let scope = WhereScope::table(schema_name, table_name, type_name, names)
6382        .under_alias(WRITTEN_ROW)
6383        .with_resolution(cache, relationships);
6384    let (where_sql, mut values) = build_where_clause(where_clause.as_ref(), 1, &scope)?;
6385
6386    // An absent or unrecognised `where` argument yields an empty clause, which
6387    // would delete every row in the table. Refuse instead.
6388    if where_sql.is_empty() {
6389        return Err(async_graphql::Error::new(format!(
6390            "delete on \"{}\" requires a `where` argument with at least one \
6391             recognised condition; refusing to delete every row",
6392            table_name
6393        )));
6394    }
6395
6396    // A relationship or computed field asked for in `returning` cannot be read
6397    // afterwards -- the rows are gone by then, which is why this used to answer
6398    // the plain columns and then fail on a non-null list. Inside one statement
6399    // the deleted rows are still a table: the delete goes in a CTE and the
6400    // projection reads from it, while the rows it points at are still there.
6401    let mut projection = String::new();
6402    let renamed = cache
6403        .get_table(&qi)
6404        .and_then(|table| rename_projection(table, "src", names));
6405    if let Some(returning) = returning {
6406        let mut param_idx = values.len() + 1;
6407        let mut alias_counter = 0usize;
6408        let embeds = build_embed_expressions(
6409            &gql_ctx.caller(),
6410            cache,
6411            relationships,
6412            type_name,
6413            "src",
6414            returning.selection_set(),
6415            max_rows,
6416            &mut alias_counter,
6417            &mut param_idx,
6418            &mut values,
6419            names,
6420        )?;
6421        let computed = match cache.get_table(&qi) {
6422            Some(table) => computed_projections(
6423                table,
6424                returning.selection_set(),
6425                "src",
6426                names,
6427                cache,
6428                &mut param_idx,
6429                &mut values,
6430            )?,
6431            None => Vec::new(),
6432        };
6433        for expression in &computed {
6434            projection.push_str(", ");
6435            projection.push_str(expression);
6436        }
6437        for (name, expression) in &embeds {
6438            projection.push_str(", ");
6439            projection.push_str(expression);
6440            projection.push_str(" AS ");
6441            projection.push_str(&postrust_sql::escape_ident(name));
6442        }
6443    }
6444
6445    let written = postrust_sql::escape_ident(WRITTEN_ROW);
6446    let sql = if projection.is_empty() && renamed.is_none() {
6447        format!(
6448            "DELETE FROM {}.{} AS {} {} RETURNING {}",
6449            postrust_sql::escape_ident(schema_name),
6450            postrust_sql::escape_ident(table_name),
6451            written,
6452            where_sql,
6453            row_json(&written, &column_types)
6454        )
6455    } else {
6456        format!(
6457            "WITH pgrst_deleted AS (DELETE FROM {}.{} AS {} {} RETURNING {}.*) \
6458             SELECT {} FROM (SELECT {}{} FROM pgrst_deleted AS src) AS pgrst_r",
6459            postrust_sql::escape_ident(schema_name),
6460            postrust_sql::escape_ident(table_name),
6461            written,
6462            where_sql,
6463            written,
6464            row_json("pgrst_r", &column_types),
6465            renamed.unwrap_or_else(|| "src.*".to_string()),
6466            projection
6467        )
6468    };
6469
6470    trace!("Executing DELETE SQL: {}", sql);
6471
6472    let mut query = sqlx::query(&sql);
6473    for val in &values {
6474        query = bind_json_value(query, val);
6475    }
6476
6477    let mut guard = write_tx(gql_ctx, pool).await?;
6478    let conn = guard.as_mut().expect("write_tx opens one");
6479    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6480
6481    let deleted: Vec<Value> = rows
6482        .iter()
6483        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6484        .map(json_to_value)
6485        .collect();
6486
6487    Ok(deleted)
6488}
6489
6490/// The predicate a role's permission adds to every read of one table.
6491///
6492/// `None` where nothing restricts the read: no permission document, an
6493/// administrator, or a permission whose filter is `{}`. The value is a
6494/// `<table>_bool_exp` like any other, because [`crate::role::permission_where`]
6495/// rewrites it into one -- which is what lets the same compiler serve both and
6496/// keeps a permission able to say everything a `where` can.
6497fn permission_predicate(
6498    caller: &crate::role::Caller<'_>,
6499    names: &crate::names::NameOverrides,
6500    schema: &str,
6501    table: &str,
6502) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6503    permission_filter(caller, names, schema, table, crate::role::Verb::Select)
6504}
6505
6506/// The same, for whichever grant the operation is asking under.
6507fn permission_filter(
6508    caller: &crate::role::Caller<'_>,
6509    names: &crate::names::NameOverrides,
6510    schema: &str,
6511    table: &str,
6512    verb: crate::role::Verb,
6513) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6514    crate::role::row_filter(caller, names, schema, table, verb)
6515        .map(|filter| filter.map(|predicate| serde_json::json!({ PERMISSION: predicate })))
6516        .map_err(|fault| coded_error(fault.code(), fault.to_string()))
6517}
6518
6519/// The key a permission's own predicate is wrapped in on its way to the
6520/// compiler.
6521///
6522/// The two halves of a `where` are the same language and are compiled by the
6523/// same code, which is what keeps a permission able to say everything a client
6524/// can. They are not evaluated by the same party: a client's half asks what
6525/// *this caller* may see, and a permission's half is the server's own
6526/// predicate, evaluated as the server.
6527///
6528/// The difference shows on a relationship. `{"Artist": {"id":
6529/// "X-Hasura-Artist-Id"}}` on `Track` grants the tracks belonging to the
6530/// caller's artist -- to a role that has no permission on `Artist` at all, and
6531/// is not meant to have one: the predicate consults that table, it does not
6532/// expose it. Compiled as the caller, it needs a permission that does not
6533/// exist and refuses itself; compiled as the server, it says what it was
6534/// written to say. `_exists` has always worked this way and says so where it
6535/// is built; this is the same rule for the relationship a permission follows.
6536///
6537/// The name cannot collide with a column: no boolean expression a client can
6538/// write has this field, because no input type declares it.
6539const PERMISSION: &str = "_permission";
6540
6541/// How many rows a permission lets one read return.
6542///
6543/// A ceiling rather than a default: a request asking for more gets this, one
6544/// asking for fewer gets what it asked for, and one asking for nothing gets
6545/// the ceiling. The same rule `PGRST_MAX_ROWS` follows, folded with it.
6546fn permission_limit(
6547    caller: &crate::role::Caller<'_>,
6548    names: &crate::names::NameOverrides,
6549    schema: &str,
6550    table: &str,
6551) -> Option<i64> {
6552    crate::role::read_limit(caller, names, schema, table)
6553}
6554
6555/// Both predicates, as one.
6556///
6557/// Written as an `_and` rather than as two clauses so that everything
6558/// downstream -- parameter numbering, relationship resolution, the `EXISTS`
6559/// that a relationship predicate becomes -- happens once, in one place, for
6560/// both halves.
6561fn and_predicates(
6562    request: Option<serde_json::Value>,
6563    permission: Option<serde_json::Value>,
6564) -> Option<serde_json::Value> {
6565    match (request, permission) {
6566        (None, None) => None,
6567        (Some(only), None) | (None, Some(only)) => Some(only),
6568        (Some(request), Some(permission)) => {
6569            Some(serde_json::json!({ "_and": [request, permission] }))
6570        }
6571    }
6572}
6573
6574/// Build a WHERE clause from a boolean expression.
6575///
6576/// The expression is the JSON form of a `<table>_bool_exp`: column names
6577/// mapping to comparisons, and `_and`, `_or` and `_not` for structure. It
6578/// nests, so this recurses.
6579///
6580/// Nothing is dropped silently. An operator that is not recognised widens the
6581/// result set if it is ignored -- every row for a query, every row for a
6582/// mutation -- so it is an error instead.
6583fn build_where_clause(
6584    where_value: Option<&serde_json::Value>,
6585    start_param_idx: usize,
6586    scope: &WhereScope<'_>,
6587) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6588    let mut values: Vec<serde_json::Value> = Vec::new();
6589    let mut param_idx = start_param_idx;
6590    let mut alias_counter = 0usize;
6591
6592    let condition = match where_value {
6593        Some(value) => build_condition(
6594            value,
6595            scope,
6596            &mut param_idx,
6597            &mut values,
6598            &mut alias_counter,
6599        )?,
6600        None => None,
6601    };
6602
6603    Ok(match condition {
6604        Some(sql) => (format!("WHERE {}", sql), values),
6605        None => (String::new(), values),
6606    })
6607}
6608
6609/// The table a boolean expression is being read against.
6610///
6611/// Column references are qualified with `sql_ref` so that a predicate over a
6612/// relationship -- which becomes a correlated `EXISTS` against another table --
6613/// can tell the two apart. Without the qualification a child column sharing a
6614/// parent column's name would silently resolve to whichever PostgreSQL
6615/// preferred.
6616///
6617/// `resolution` is absent where a caller has no schema cache to hand, which is
6618/// how the mutation paths are called. A relationship predicate then reports
6619/// that it cannot be resolved rather than being read as a column.
6620pub struct WhereScope<'a> {
6621    /// The table itself, for the comparisons that need a column's type.
6622    qualified: postrust_core::api_request::QualifiedIdentifier,
6623    /// How to refer to this table's columns in SQL.
6624    sql_ref: String,
6625    /// How to refer to this table's *row*, which is what a function taking the
6626    /// row is passed. A qualified name reads as a column reference there, so
6627    /// this is the bare alias.
6628    row_ref: String,
6629    /// The GraphQL type name, which is the key into the relationship map.
6630    type_name: String,
6631    /// How to refer to the columns of the table the predicate started at, and
6632    /// which table that is.
6633    ///
6634    /// A column comparison written `["$", "name"]` means that table's column,
6635    /// however many relationships the predicate has descended through since --
6636    /// which is what makes the form worth having. Equal to this table's own
6637    /// until an `EXISTS` is entered.
6638    root_ref: String,
6639    root_qualified: postrust_core::api_request::QualifiedIdentifier,
6640    /// The names this table's columns are exposed under, so a predicate
6641    /// written against a field can be written as SQL against a column.
6642    names: &'a crate::names::NameOverrides,
6643    resolution: Option<WhereResolution<'a>>,
6644    /// Who is asking, so that a predicate following a relationship narrows the
6645    /// far side to what that caller may read.
6646    ///
6647    /// Unrestricted unless a scope is told otherwise, which is what leaves
6648    /// every path that has no caller to hand -- the mutation ones -- behaving
6649    /// as it did.
6650    caller: Option<crate::role::Caller<'a>>,
6651}
6652
6653/// What a scope needs to follow a relationship.
6654struct WhereResolution<'a> {
6655    cache: &'a SchemaCache,
6656    relationships: &'a HashMap<String, Vec<RelationshipField>>,
6657}
6658
6659impl<'a> WhereScope<'a> {
6660    /// A scope over a table addressed by its qualified name.
6661    pub fn table(
6662        schema: &str,
6663        table: &str,
6664        type_name: &str,
6665        names: &'a crate::names::NameOverrides,
6666    ) -> Self {
6667        Self {
6668            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6669            sql_ref: format!(
6670                "{}.{}",
6671                postrust_sql::escape_ident(schema),
6672                postrust_sql::escape_ident(table)
6673            ),
6674            row_ref: postrust_sql::escape_ident(table),
6675            type_name: type_name.to_string(),
6676            root_ref: format!(
6677                "{}.{}",
6678                postrust_sql::escape_ident(schema),
6679                postrust_sql::escape_ident(table)
6680            ),
6681            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6682            names,
6683            resolution: None,
6684            caller: None,
6685        }
6686    }
6687
6688    /// The same, referred to by an alias rather than by its name.
6689    ///
6690    /// A statement that writes a table has to name that table's row in
6691    /// `RETURNING`, and a bare table name there is read as a *column* first --
6692    /// so `area.area`, a geography column in a table of the same name, made
6693    /// `to_jsonb("area")` the shape rather than the row. The alias is a name
6694    /// no column has.
6695    fn under_alias(mut self, alias: &str) -> Self {
6696        // The root moves with it: this is the outermost table being renamed,
6697        // not a step into another one.
6698        if self.root_ref == self.sql_ref {
6699            self.root_ref = postrust_sql::escape_ident(alias);
6700        }
6701        self.sql_ref = postrust_sql::escape_ident(alias);
6702        self.row_ref = postrust_sql::escape_ident(alias);
6703        self
6704    }
6705
6706    /// The same, able to follow relationships.
6707    fn with_resolution(
6708        mut self,
6709        cache: &'a SchemaCache,
6710        relationships: &'a HashMap<String, Vec<RelationshipField>>,
6711    ) -> Self {
6712        self.resolution = Some(WhereResolution {
6713            cache,
6714            relationships,
6715        });
6716        self
6717    }
6718
6719    /// A scope over an aliased table, able to follow relationships.
6720    #[allow(clippy::too_many_arguments)]
6721    fn for_alias(
6722        schema: &str,
6723        table: &str,
6724        alias: &str,
6725        type_name: &str,
6726        cache: &'a SchemaCache,
6727        relationships: &'a HashMap<String, Vec<RelationshipField>>,
6728        names: &'a crate::names::NameOverrides,
6729    ) -> Self {
6730        Self {
6731            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6732            sql_ref: postrust_sql::escape_ident(alias),
6733            row_ref: postrust_sql::escape_ident(alias),
6734            type_name: type_name.to_string(),
6735            root_ref: postrust_sql::escape_ident(alias),
6736            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6737            names,
6738            resolution: Some(WhereResolution {
6739                cache,
6740                relationships,
6741            }),
6742            caller: None,
6743        }
6744    }
6745
6746    /// A scope over an aliased table, for the inside of an `EXISTS`.
6747    /// A scope over the *other* table of a relationship, under an alias.
6748    ///
6749    /// The child's own qualified name, not the parent's: a comparison inside
6750    /// an `EXISTS` is against the child's columns, so it is the child's types
6751    /// and the child's renames that decide how to write it.
6752    fn aliased(
6753        schema: &str,
6754        table: &str,
6755        alias: &str,
6756        type_name: &str,
6757        from: &WhereScope<'a>,
6758    ) -> Self {
6759        Self {
6760            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6761            sql_ref: postrust_sql::escape_ident(alias),
6762            row_ref: postrust_sql::escape_ident(alias),
6763            type_name: type_name.to_string(),
6764            // Carried, not replaced: `$` names the table the predicate began
6765            // at, which is the whole point of descending with it.
6766            root_ref: from.root_ref.clone(),
6767            root_qualified: from.root_qualified.clone(),
6768            names: from.names,
6769            resolution: from.resolution.as_ref().map(|r| WhereResolution {
6770                cache: r.cache,
6771                relationships: r.relationships,
6772            }),
6773            // Carried across the relationship: the caller does not change on
6774            // the way into an EXISTS, and the far side has its own permission
6775            // to be narrowed by.
6776            caller: from.caller,
6777        }
6778    }
6779
6780    /// Narrow what this scope's predicates may reach to one caller.
6781    pub fn for_caller(mut self, caller: crate::role::Caller<'a>) -> Self {
6782        self.caller = Some(caller);
6783        self
6784    }
6785
6786    /// The same scope with no caller: what a permission's own predicate is
6787    /// evaluated as. See `PERMISSION`.
6788    fn as_system(&self) -> WhereScope<'a> {
6789        WhereScope {
6790            qualified: self.qualified.clone(),
6791            sql_ref: self.sql_ref.clone(),
6792            row_ref: self.row_ref.clone(),
6793            type_name: self.type_name.clone(),
6794            // The root moves here. `$` in a permission names the table the
6795            // permission is *written on*, which is this one -- not whatever
6796            // table the query happened to start at. Reading `dml_rbp_tasks`
6797            // through a project's `where: {tasks: {...}}` compared the task's
6798            // grant against a column of `dml_rbp_projects` until this, and
6799            // answered with rows the role may not see.
6800            root_ref: self.sql_ref.clone(),
6801            root_qualified: self.qualified.clone(),
6802            names: self.names,
6803            resolution: self.resolution.as_ref().map(|r| WhereResolution {
6804                cache: r.cache,
6805                relationships: r.relationships,
6806            }),
6807            caller: None,
6808        }
6809    }
6810
6811    /// What a field name refers to, as SQL: a column of this table, or the
6812    /// call that produces a computed field.
6813    ///
6814    /// A computed field is in the boolean expression because it is on the type
6815    /// -- `where: {sum_float_offset: {_gt: 5}}` is a question a client can
6816    /// write -- and reading it as a column answered `column
6817    /// float_test.sum_float_offset does not exist`.
6818    fn column(&self, name: &str) -> String {
6819        let column = self.column_name(name);
6820        let plain = format!("{}.{}", self.sql_ref, postrust_sql::escape_ident(column));
6821        let Some(cache) = self.resolution.as_ref().map(|r| r.cache) else {
6822            return plain;
6823        };
6824        let Some(table) = cache.get_table(&self.qualified) else {
6825            return plain;
6826        };
6827        if table.get_column(column).is_some() {
6828            return plain;
6829        }
6830        let function = self
6831            .names
6832            .computed_source(&table.schema, &table.name, name)
6833            .unwrap_or(name);
6834        match table.get_computed_column(function) {
6835            Some(definition) => {
6836                computed_call(definition, &format!("{}.*", self.row_ref), &self.row_ref)
6837            }
6838            None => plain,
6839        }
6840    }
6841
6842    /// The column behind a field name.
6843    fn column_name<'n>(&'n self, name: &'n str) -> &'n str {
6844        self.names
6845            .column_source(&self.qualified.schema, &self.qualified.name, name)
6846            .unwrap_or(name)
6847    }
6848
6849    /// The PostgreSQL type of one of this table's columns, where it can be
6850    /// found. A spatial comparison needs it: the same function takes a
6851    /// geometry or a geography and the operand has to be cast to match.
6852    fn column_type(&self, name: &str) -> Option<String> {
6853        let cache = self.resolution.as_ref()?.cache;
6854        let table = cache.get_table(&self.qualified)?;
6855        table
6856            .get_column(self.column_name(name))
6857            .map(|c| c.nominal_type.clone())
6858    }
6859
6860    /// The other side of a column-to-column comparison, as SQL.
6861    ///
6862    /// Hasura writes the operand two ways. A bare name is a column of *this*
6863    /// table -- `{"bid_price": {"$cgt": "price"}}` compares two columns of one
6864    /// auction. `["$", "name"]` is a column of the table the permission is
6865    /// written on, which is what makes the form useful inside a predicate over
6866    /// a relationship: `{"item": {"item_quantity": {"_cgte": ["$",
6867    /// "order_cart_quantity"]}}}` asks whether the item has as many in stock
6868    /// as the cart is asking for, and the cart is the outer row.
6869    ///
6870    /// Nothing else is accepted. A longer path would walk relationships to
6871    /// reach the column, which Hasura allows and no case here writes -- and
6872    /// reading it wrongly would widen a permission rather than narrow it.
6873    fn other_column(
6874        &self,
6875        operand: &serde_json::Value,
6876        op: &str,
6877    ) -> Result<String, async_graphql::Error> {
6878        use serde_json::Value;
6879
6880        let refused = |what: &str| {
6881            async_graphql::Error::new(format!(
6882                "the \"{}\" comparison takes the name of a column, {}",
6883                op, what
6884            ))
6885        };
6886        let (against, qualified, name) = match operand {
6887            Value::String(name) => (&self.sql_ref, &self.qualified, name.as_str()),
6888            Value::Array(path) => match path.as_slice() {
6889                [Value::String(name)] => (&self.sql_ref, &self.qualified, name.as_str()),
6890                [Value::String(root), Value::String(name)] if root == "$" => {
6891                    (&self.root_ref, &self.root_qualified, name.as_str())
6892                }
6893                _ => return Err(refused("or [\"$\", \"a column\"]")),
6894            },
6895            _ => return Err(refused("written as a string")),
6896        };
6897        // Read against whichever table it belongs to, since a rename is
6898        // per-table and the two are not the same table.
6899        let column = self
6900            .names
6901            .column_source(&qualified.schema, &qualified.name, name)
6902            .unwrap_or(name);
6903        Ok(format!(
6904            "{}.{}",
6905            against,
6906            postrust_sql::escape_ident(column)
6907        ))
6908    }
6909
6910    /// The relationship a predicate is following, where there is one.
6911    ///
6912    /// Looked up in the schema's own map first, which is the answer for every
6913    /// relationship the role can read. A permission may name one it cannot:
6914    /// role `Artist` reads `Track` and filters it by `{"Artist": {"id": ...}}`
6915    /// while having no permission on `Artist` at all, so that relationship is
6916    /// in no schema this role is served. The catalogue still has it -- the
6917    /// cache a scope resolves against is the unreduced one -- and the
6918    /// relationship is derived from it there.
6919    ///
6920    /// Deriving it cannot let a client reach a hidden table: a key that is not
6921    /// a field of the boolean expression is refused before any of this runs,
6922    /// and the only writer of such a key is a permission.
6923    fn relationship(&self, name: &str) -> Option<std::borrow::Cow<'a, RelationshipField>> {
6924        let resolution = self.resolution.as_ref()?;
6925        let published = resolution
6926            .relationships
6927            .get(&self.type_name)
6928            .and_then(|fields| fields.iter().find(|r| r.name == name));
6929        if let Some(found) = published {
6930            return Some(std::borrow::Cow::Borrowed(found));
6931        }
6932
6933        let table = resolution.cache.get_table(&self.qualified)?;
6934
6935        // A relationship metadata declares, which may be a mapped join the
6936        // catalogue has never heard of. `message.members` is one, and a
6937        // permission on `message` is written through it -- so this is not a
6938        // convenience: without it that filter cannot be compiled at all and
6939        // the read is refused rather than narrowed.
6940        if let Some(declared) = self
6941            .names
6942            .declared_relationships(&table.schema, &table.name)
6943        {
6944            if let Some(entry) = declared.iter().find(|entry| entry.name == name) {
6945                // Reached by a mapping, which the catalogue has never heard
6946                // of.
6947                let target = entry
6948                    .target(&table.schema)
6949                    .map(|(_, target)| target)
6950                    .unwrap_or_default();
6951                if let Some(field) = crate::schema::mapped_relationship_field(
6952                    entry,
6953                    table,
6954                    resolution.cache,
6955                    &table.schema,
6956                    &target,
6957                    &target,
6958                    &target,
6959                ) {
6960                    return Some(std::borrow::Cow::Owned(field));
6961                }
6962                // Or by a key, under the name the declaration gives it --
6963                // which is the only place that name is written down now that
6964                // a declaration carries it.
6965                if let Some(key) = entry.using.as_deref() {
6966                    if let Some(found) = resolution
6967                        .cache
6968                        .get_relationships(&self.qualified, &table.schema)
6969                        .into_iter()
6970                        .flatten()
6971                        .find(|rel| {
6972                            crate::schema::relationship_keys(rel)
6973                                .iter()
6974                                .any(|found| found == key)
6975                        })
6976                    {
6977                        return Some(std::borrow::Cow::Owned(
6978                            crate::schema::relationship::RelationshipField::from_relationship_named(
6979                                found,
6980                                &found.foreign_table().name,
6981                                Some(&entry.name),
6982                            ),
6983                        ));
6984                    }
6985                }
6986            }
6987        }
6988
6989        resolution
6990            .cache
6991            .get_relationships(&self.qualified, &table.schema)?
6992            .iter()
6993            .find_map(|rel| {
6994                let given = crate::schema::relationship_keys(rel)
6995                    .iter()
6996                    .find_map(|key| self.names.relationship(&table.schema, &table.name, key));
6997                // The target's own name stands in for its GraphQL type, which
6998                // this role has none of. Nothing reads it but the lookup
6999                // above, and that lookup will land here again.
7000                let field = RelationshipField::from_relationship_named(
7001                    rel,
7002                    &rel.foreign_table().name,
7003                    given,
7004                );
7005                (field.name == name).then_some(std::borrow::Cow::Owned(field))
7006            })
7007    }
7008}
7009
7010/// The SQL operator behind a column-to-column comparison, where this is one.
7011///
7012/// `_ceq`, `_cne`, `_cgt`, `_cgte`, `_clt`, `_clte`: the `c` is for column.
7013/// Only a permission writes these -- no boolean expression a client can write
7014/// has them, because a client has no way to name the row a comparison would be
7015/// against. A permission does: `{"bid_price": {"$cgt": "price"}}` on `auction`
7016/// grants the rows whose bid has passed the reserve, which is a fact about the
7017/// row rather than about the caller.
7018fn column_comparison(op: &str) -> Option<&'static str> {
7019    Some(match op {
7020        "_ceq" => "=",
7021        "_cne" => "<>",
7022        "_cgt" => ">",
7023        "_cgte" => ">=",
7024        "_clt" => "<",
7025        "_clte" => "<=",
7026        _ => return None,
7027    })
7028}
7029
7030/// One boolean expression, as SQL. `None` where it constrains nothing.
7031fn build_condition(
7032    value: &serde_json::Value,
7033    scope: &WhereScope<'_>,
7034    param_idx: &mut usize,
7035    values: &mut Vec<serde_json::Value>,
7036    alias_counter: &mut usize,
7037) -> Result<Option<String>, async_graphql::Error> {
7038    let serde_json::Value::Object(map) = value else {
7039        return Ok(None);
7040    };
7041
7042    let mut conditions: Vec<String> = Vec::new();
7043
7044    for (key, val) in map {
7045        match key.as_str() {
7046            // An empty `_and` is true and an empty `_or` is false, which is
7047            // what SQL's own identities give: nothing to AND constrains
7048            // nothing, nothing to OR matches nothing.
7049            "_and" | "_or" => {
7050                let members = val.as_array().ok_or_else(|| {
7051                    async_graphql::Error::new(format!("\"{}\" takes a list of expressions", key))
7052                })?;
7053                let mut parts = Vec::new();
7054                for member in members {
7055                    if let Some(sql) =
7056                        build_condition(member, scope, param_idx, values, alias_counter)?
7057                    {
7058                        parts.push(sql);
7059                    }
7060                }
7061                if parts.is_empty() {
7062                    if key == "_or" {
7063                        conditions.push("false".to_string());
7064                    }
7065                    continue;
7066                }
7067                let joiner = if key == "_and" { " AND " } else { " OR " };
7068                conditions.push(format!("({})", parts.join(joiner)));
7069            }
7070            "_not" => {
7071                if let Some(sql) = build_condition(val, scope, param_idx, values, alias_counter)? {
7072                    conditions.push(format!("NOT ({})", sql));
7073                }
7074            }
7075            // A permission's own predicate, which is the server's rather
7076            // than the caller's. See `PERMISSION`.
7077            PERMISSION => {
7078                if let Some(sql) =
7079                    build_condition(val, &scope.as_system(), param_idx, values, alias_counter)?
7080                {
7081                    conditions.push(sql);
7082                }
7083            }
7084            // A question about a table nothing relates this one to. Only a
7085            // permission writes it; no client can, because no boolean
7086            // expression has the field.
7087            "_exists" => {
7088                conditions.push(table_exists_sql(
7089                    val,
7090                    scope,
7091                    param_idx,
7092                    values,
7093                    alias_counter,
7094                )?);
7095            }
7096            // A relationship is filtered by filtering the rows at its other
7097            // end. `where: {articles: {title: {_eq: "x"}}}` keeps the authors
7098            // that have such an article -- an `EXISTS` correlated back to the
7099            // parent row, which is also what it means for a to-one
7100            // relationship, where the subquery simply cannot match twice.
7101            name if scope.relationship(name).is_some() => {
7102                let relationship = scope.relationship(name).expect("just checked");
7103                conditions.push(exists_sql(
7104                    &relationship,
7105                    val,
7106                    scope,
7107                    param_idx,
7108                    values,
7109                    alias_counter,
7110                )?);
7111            }
7112            // A question about the whole related set rather than about any
7113            // one row of it. `where: {articles_aggregate: {count: {predicate:
7114            // {_gt: 2}}}}` keeps the authors with more than two articles,
7115            // which no `EXISTS` over one article can say.
7116            name if scope.column_type(name).is_none()
7117                && name
7118                    .strip_suffix("_aggregate")
7119                    .and_then(|rel| scope.relationship(rel))
7120                    .is_some_and(|rel| rel.is_list) =>
7121            {
7122                let relationship = name
7123                    .strip_suffix("_aggregate")
7124                    .and_then(|rel| scope.relationship(rel))
7125                    .expect("just checked");
7126                conditions.push(aggregate_predicate_sql(
7127                    &relationship,
7128                    val,
7129                    scope,
7130                    param_idx,
7131                    values,
7132                    alias_counter,
7133                )?);
7134            }
7135            column => {
7136                let quoted = scope.column(column);
7137                match val {
7138                    serde_json::Value::Object(ops) => {
7139                        let column_type = scope.column_type(column);
7140                        for (op, operand) in ops {
7141                            // A comparison against another column rather than
7142                            // against a value, which only a permission can
7143                            // write. It needs the scope to say *which* column,
7144                            // so it is answered here rather than in
7145                            // `comparison_sql`, which sees one side.
7146                            if let Some(other) = column_comparison(op) {
7147                                conditions.push(format!(
7148                                    "{} {} {}",
7149                                    quoted,
7150                                    other,
7151                                    scope.other_column(operand, op)?
7152                                ));
7153                                continue;
7154                            }
7155                            conditions.push(comparison_sql(
7156                                &quoted,
7157                                column,
7158                                column_type.as_deref(),
7159                                op,
7160                                operand,
7161                                param_idx,
7162                                values,
7163                            )?);
7164                        }
7165                    }
7166                    // `{id: 1}` rather than `{id: {_eq: 1}}`. Not a spelling
7167                    // Hasura accepts, but one that costs nothing to read and
7168                    // that hand-written queries reach for.
7169                    other => {
7170                        conditions.push(format!("{} = ${}", quoted, param_idx));
7171                        values.push(other.clone());
7172                        *param_idx += 1;
7173                    }
7174                }
7175            }
7176        }
7177    }
7178
7179    Ok(if conditions.is_empty() {
7180        None
7181    } else if conditions.len() == 1 {
7182        conditions.pop()
7183    } else {
7184        Some(conditions.join(" AND "))
7185    })
7186}
7187
7188/// A predicate over a relationship, as a correlated `EXISTS`.
7189///
7190/// Only a plain key join is expressible this way. A many-to-many through a
7191/// junction and a computed relationship both need more than a pair of columns
7192/// to correlate on, and saying so is the only honest answer -- reading the
7193/// predicate as though it constrained nothing would return every parent row.
7194fn exists_sql(
7195    relationship: &RelationshipField,
7196    child_expression: &serde_json::Value,
7197    scope: &WhereScope<'_>,
7198    param_idx: &mut usize,
7199    values: &mut Vec<serde_json::Value>,
7200    alias_counter: &mut usize,
7201) -> Result<String, async_graphql::Error> {
7202    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7203        async_graphql::Error::new(format!(
7204            "filtering on the relationship \"{}\" is not available here",
7205            relationship.name
7206        ))
7207    })?;
7208
7209    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7210        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7211
7212    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7213        return Err(async_graphql::Error::new(format!(
7214            "filtering on \"{}\" is not supported: it is reached through a junction \
7215             rather than by a key",
7216            relationship.name
7217        )));
7218    }
7219
7220    *alias_counter += 1;
7221    let alias = format!("pgrst_rel_{}", alias_counter);
7222    let child_scope = WhereScope::aliased(
7223        &plan.foreign_schema,
7224        &plan.foreign_table,
7225        &alias,
7226        &relationship.target_type,
7227        scope,
7228    );
7229
7230    // A computed relationship is correlated by argument -- the function takes
7231    // the parent row -- where a key relationship is correlated by columns.
7232    let (source, mut correlation) = match &plan.function {
7233        Some(function) => (
7234            format!(
7235                "{}.{}({})",
7236                postrust_sql::escape_ident(&function.schema),
7237                postrust_sql::escape_ident(&function.name),
7238                scope.row_ref
7239            ),
7240            Vec::new(),
7241        ),
7242        None => {
7243            let mut columns = Vec::with_capacity(plan.columns.len());
7244            for (parent_column, child_column) in &plan.columns {
7245                columns.push(format!(
7246                    "{} = {}",
7247                    scope.column(parent_column),
7248                    child_scope.column(child_column)
7249                ));
7250            }
7251            (
7252                format!(
7253                    "{}.{}",
7254                    postrust_sql::escape_ident(&plan.foreign_schema),
7255                    postrust_sql::escape_ident(&plan.foreign_table)
7256                ),
7257                columns,
7258            )
7259        }
7260    };
7261
7262    let child_condition = build_condition(
7263        child_expression,
7264        &child_scope,
7265        param_idx,
7266        values,
7267        alias_counter,
7268    )?;
7269    if let Some(sql) = child_condition {
7270        correlation.push(format!("({})", sql));
7271    }
7272
7273    // The far side's own permission. `where: {author: {name: {_eq: "Ann"}}}`
7274    // asks whether a related row exists, and a row this caller may not read is
7275    // one it may not learn the existence of either -- so the child's filter
7276    // narrows the EXISTS exactly as it narrows a read of the same table.
7277    if let Some(caller) = &child_scope.caller {
7278        // Through `permission_predicate` rather than `role::read_predicate`,
7279        // so the far side's filter is marked as the server's own -- see
7280        // `PERMISSION`. Without the mark it was compiled as the caller, and a
7281        // filter that follows a relationship of its own then needed a
7282        // permission on *that* table: `dml_rbp_tasks` is filtered through
7283        // `grants`, which the role reading tasks may not read.
7284        let permission = permission_predicate(
7285            caller,
7286            child_scope.names,
7287            &plan.foreign_schema,
7288            &plan.foreign_table,
7289        )?;
7290        if let Some(predicate) = permission {
7291            if let Some(sql) =
7292                build_condition(&predicate, &child_scope, param_idx, values, alias_counter)?
7293            {
7294                correlation.push(format!("({})", sql));
7295            }
7296        }
7297    }
7298    if correlation.is_empty() {
7299        correlation.push("true".to_string());
7300    }
7301
7302    Ok(format!(
7303        "EXISTS (SELECT 1 FROM {} AS {} WHERE {})",
7304        source,
7305        postrust_sql::escape_ident(&alias),
7306        correlation.join(" AND ")
7307    ))
7308}
7309
7310/// `_exists`, the predicate only a permission can write, as SQL.
7311///
7312/// ```json
7313/// {"_exists": {"_table": "user", "_where": {"id": "X-Hasura-User-Id",
7314///                                           "is_admin": true}}}
7315/// ```
7316///
7317/// It asks whether a row exists in *another* table -- one no foreign key
7318/// relates to this one. That is how Hasura writes "the caller is an
7319/// administrator" without asking the caller to say so: the row that decides it
7320/// lives in a table of its own, and whether the account this predicate guards
7321/// is readable does not depend on which account it is.
7322///
7323/// So the subselect is **uncorrelated**, which is the whole difference from
7324/// the `EXISTS` a relationship predicate builds. There are no key columns to
7325/// join on -- if there were, the permission would have been written as a
7326/// relationship -- and the `_where` is read against the named table's columns
7327/// alone.
7328///
7329/// The caller's own permissions are not applied inside it, and that is not an
7330/// oversight. The point of `_exists` is to consult a table the role has no
7331/// access to: in Hasura's corpus the role that reads `account` through this
7332/// predicate may not read `public.user` at all. Narrowing the subselect to
7333/// what the caller may read would make the predicate refuse itself.
7334fn table_exists_sql(
7335    spec: &serde_json::Value,
7336    scope: &WhereScope<'_>,
7337    param_idx: &mut usize,
7338    values: &mut Vec<serde_json::Value>,
7339    alias_counter: &mut usize,
7340) -> Result<String, async_graphql::Error> {
7341    let Some((schema, table)) = crate::role::exists_target(spec, &scope.qualified.schema) else {
7342        return Err(async_graphql::Error::new(
7343            "\"_exists\" needs a \"_table\" to look in",
7344        ));
7345    };
7346
7347    // The name comes from a permission document rather than from a request,
7348    // but it still reaches SQL, and a table that is not there would reach it as
7349    // a syntax error at the far end rather than as an answer here.
7350    let qualified = postrust_core::api_request::QualifiedIdentifier::new(&schema, &table);
7351    if scope
7352        .resolution
7353        .as_ref()
7354        .is_some_and(|r| r.cache.get_table(&qualified).is_none())
7355    {
7356        return Err(async_graphql::Error::new(format!(
7357            "\"_exists\" names a table this server does not have: \"{}.{}\"",
7358            schema, table
7359        )));
7360    }
7361
7362    *alias_counter += 1;
7363    let alias = format!("pgrst_exists_{}", alias_counter);
7364    // The type this table is exposed as, which is the key into the
7365    // relationship map -- so a `_where` may follow the named table's own
7366    // relationships, the way any other predicate over it could.
7367    let type_name = scope
7368        .names
7369        .base_name(&schema, &table)
7370        .unwrap_or(&table)
7371        .to_string();
7372    let mut inner = WhereScope::aliased(&schema, &table, &alias, &type_name, scope);
7373    // See above: the predicate is the permission, so it is not itself
7374    // subject to one.
7375    inner.caller = None;
7376
7377    let condition = match spec.get("_where") {
7378        Some(predicate) => build_condition(predicate, &inner, param_idx, values, alias_counter)?,
7379        None => None,
7380    };
7381
7382    Ok(format!(
7383        "EXISTS (SELECT 1 FROM {}.{} AS {} WHERE {})",
7384        postrust_sql::escape_ident(&schema),
7385        postrust_sql::escape_ident(&table),
7386        postrust_sql::escape_ident(&alias),
7387        condition.unwrap_or_else(|| "true".to_string())
7388    ))
7389}
7390
7391/// A predicate over an aggregate of a related set, as SQL.
7392///
7393/// `{count: {predicate: {_gt: 2}, filter: {...}}}` becomes a scalar subselect
7394/// correlated back to the parent row, compared the way any column is:
7395///
7396/// ```text
7397/// (SELECT count(*) FROM "public"."article" AS pgrst_rel_1
7398///   WHERE "author"."id" = pgrst_rel_1."author_id" AND (...)) > $1
7399/// ```
7400///
7401/// A scalar subselect rather than an `EXISTS`, because the answer is a number
7402/// or a truth value and not whether a row is there -- and because over no rows
7403/// at all `count` is zero and `bool_and` is null, which is what SQL says and
7404/// what a client asking "no articles" is relying on.
7405fn aggregate_predicate_sql(
7406    relationship: &RelationshipField,
7407    expression: &serde_json::Value,
7408    scope: &WhereScope<'_>,
7409    param_idx: &mut usize,
7410    values: &mut Vec<serde_json::Value>,
7411    alias_counter: &mut usize,
7412) -> Result<String, async_graphql::Error> {
7413    let serde_json::Value::Object(over) = expression else {
7414        return Ok("true".to_string());
7415    };
7416
7417    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7418        async_graphql::Error::new(format!(
7419            "filtering on the relationship \"{}\" is not available here",
7420            relationship.name
7421        ))
7422    })?;
7423    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7424        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7425    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7426        return Err(async_graphql::Error::new(format!(
7427            "aggregating over \"{}\" is not supported: it is reached through a junction \
7428             rather than by a key",
7429            relationship.name
7430        )));
7431    }
7432
7433    let mut conditions: Vec<String> = Vec::new();
7434    for (function, spec) in over {
7435        if spec.is_null() {
7436            continue;
7437        }
7438        let serde_json::Value::Object(spec) = spec else {
7439            continue;
7440        };
7441        let Some(predicate) = spec.get("predicate").filter(|p| !p.is_null()) else {
7442            return Err(validation_error(format!(
7443                "\"{}\" over \"{}\" needs a predicate",
7444                function, relationship.name
7445            )));
7446        };
7447
7448        *alias_counter += 1;
7449        let alias = format!("pgrst_agg_{}", alias_counter);
7450        let child_scope = WhereScope::aliased(
7451            &plan.foreign_schema,
7452            &plan.foreign_table,
7453            &alias,
7454            &relationship.target_type,
7455            scope,
7456        );
7457
7458        // Correlated the way an `EXISTS` over the same relationship is: by
7459        // the key, or by handing the parent row to the function.
7460        let (source, mut correlation) = match &plan.function {
7461            Some(routine) => (
7462                format!(
7463                    "{}.{}({})",
7464                    postrust_sql::escape_ident(&routine.schema),
7465                    postrust_sql::escape_ident(&routine.name),
7466                    scope.row_ref
7467                ),
7468                Vec::new(),
7469            ),
7470            None => {
7471                let mut columns = Vec::with_capacity(plan.columns.len());
7472                for (parent_column, child_column) in &plan.columns {
7473                    columns.push(format!(
7474                        "{} = {}",
7475                        scope.column(parent_column),
7476                        child_scope.column(child_column)
7477                    ));
7478                }
7479                (
7480                    format!(
7481                        "{}.{}",
7482                        postrust_sql::escape_ident(&plan.foreign_schema),
7483                        postrust_sql::escape_ident(&plan.foreign_table)
7484                    ),
7485                    columns,
7486                )
7487            }
7488        };
7489        // `filter` narrows what is aggregated, and is an ordinary boolean
7490        // expression over the child.
7491        if let Some(filter) = spec.get("filter").filter(|f| !f.is_null()) {
7492            if let Some(sql) =
7493                build_condition(filter, &child_scope, param_idx, values, alias_counter)?
7494            {
7495                correlation.push(format!("({})", sql));
7496            }
7497        }
7498        if correlation.is_empty() {
7499            correlation.push("true".to_string());
7500        }
7501
7502        let arguments = spec.get("arguments").filter(|a| !a.is_null());
7503        let distinct = matches!(spec.get("distinct"), Some(serde_json::Value::Bool(true)));
7504        let (aggregate, operand_type) = match function.as_str() {
7505            "count" => {
7506                let columns: Vec<String> = match arguments {
7507                    Some(serde_json::Value::Array(items)) => items
7508                        .iter()
7509                        .filter_map(|item| item.as_str())
7510                        .map(|column| child_scope.column(column))
7511                        .collect(),
7512                    Some(serde_json::Value::String(one)) => vec![child_scope.column(one)],
7513                    _ => Vec::new(),
7514                };
7515                let counted = match columns.len() {
7516                    0 => "*".to_string(),
7517                    1 => columns.into_iter().next().unwrap_or_default(),
7518                    _ => format!("({})", columns.join(", ")),
7519                };
7520                let counted = match distinct && counted != "*" {
7521                    true => format!("DISTINCT {}", counted),
7522                    false => counted,
7523                };
7524                (format!("count({})", counted), "integer")
7525            }
7526            "bool_and" | "bool_or" => {
7527                let Some(column) = arguments.and_then(|a| a.as_str()) else {
7528                    return Err(validation_error(format!(
7529                        "\"{}\" over \"{}\" needs the column to fold",
7530                        function, relationship.name
7531                    )));
7532                };
7533                (
7534                    format!("{}({})", function, child_scope.column(column)),
7535                    "boolean",
7536                )
7537            }
7538            other => {
7539                return Err(validation_error(format!(
7540                    "\"{}\" is not an aggregate a predicate can be written over",
7541                    other
7542                )))
7543            }
7544        };
7545
7546        let subselect = format!(
7547            "(SELECT {} FROM {} AS {} WHERE {})",
7548            aggregate,
7549            source,
7550            postrust_sql::escape_ident(&alias),
7551            correlation.join(" AND ")
7552        );
7553        let serde_json::Value::Object(operators) = predicate else {
7554            return Err(validation_error(format!(
7555                "the predicate on \"{}\" is not a comparison",
7556                function
7557            )));
7558        };
7559        for (operator, operand) in operators {
7560            conditions.push(comparison_sql(
7561                &subselect,
7562                function,
7563                Some(operand_type),
7564                operator,
7565                operand,
7566                param_idx,
7567                values,
7568            )?);
7569        }
7570    }
7571
7572    Ok(match conditions.is_empty() {
7573        true => "true".to_string(),
7574        false => format!("({})", conditions.join(" AND ")),
7575    })
7576}
7577
7578/// One spatial comparison, as a PostGIS call.
7579///
7580/// The operand arrives as GeoJSON, which is what Hasura accepts and what a
7581/// client sends, so it is parsed by `ST_GeomFromGeoJSON` rather than bound as
7582/// a shape. The cast that follows is the column's own type: the same function
7583/// takes a geometry or a geography and picking the wrong one is not an
7584/// overload PostGIS has.
7585#[allow(clippy::too_many_arguments)]
7586fn postgis_sql(
7587    quoted: &str,
7588    column_type: Option<&str>,
7589    function: &str,
7590    op: &str,
7591    operand: &serde_json::Value,
7592    param_idx: &mut usize,
7593    values: &mut Vec<serde_json::Value>,
7594) -> Result<String, async_graphql::Error> {
7595    let is_geography = column_type == Some("geography");
7596    let mut shape = |value: &serde_json::Value, param_idx: &mut usize| -> String {
7597        let placeholder = format!("${}", param_idx);
7598        *param_idx += 1;
7599        values.push(value.clone());
7600        match is_geography {
7601            true => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
7602            false => format!("ST_GeomFromGeoJSON({})", placeholder),
7603        }
7604    };
7605
7606    match op {
7607        // `{distance, from}`: the shape and how far from it.
7608        "_st_d_within" | "_st_3d_d_within" => {
7609            let serde_json::Value::Object(spec) = operand else {
7610                return Err(async_graphql::Error::new(format!(
7611                    "\"{}\" takes {{distance, from}}",
7612                    op
7613                )));
7614            };
7615            let Some(from) = spec.get("from") else {
7616                return Err(async_graphql::Error::new(format!(
7617                    "\"{}\" needs a shape to measure from",
7618                    op
7619                )));
7620            };
7621            let Some(distance) = spec.get("distance") else {
7622                return Err(async_graphql::Error::new(format!(
7623                    "\"{}\" needs a distance",
7624                    op
7625                )));
7626            };
7627            let from_sql = shape(from, param_idx);
7628            let distance_sql = format!("${}", param_idx);
7629            *param_idx += 1;
7630            values.push(distance.clone());
7631            // On a sphere the answer depends on which sphere: `ST_DWithin`
7632            // over geography measures on the spheroid by default and on a
7633            // perfect sphere when told not to, and the two disagree by enough
7634            // to change which rows come back. Geometry has no such argument.
7635            let spheroid = match (is_geography, spec.get("use_spheroid")) {
7636                (true, Some(use_spheroid)) if !use_spheroid.is_null() => {
7637                    let placeholder = format!("${}", param_idx);
7638                    *param_idx += 1;
7639                    values.push(use_spheroid.clone());
7640                    format!(", {}::boolean", placeholder)
7641                }
7642                _ => String::new(),
7643            };
7644            Ok(format!(
7645                "{}({}, {}, {}::float8{})",
7646                function, quoted, from_sql, distance_sql, spheroid
7647            ))
7648        }
7649        // A raster against another raster, bound as one rather than parsed.
7650        "_st_intersects_rast" => {
7651            let placeholder = format!("${}", param_idx);
7652            *param_idx += 1;
7653            values.push(operand.clone());
7654            Ok(format!("{}({}, {}::raster)", function, quoted, placeholder))
7655        }
7656        // A raster against a shape, in one band or in any.
7657        "_st_intersects_geom_nband" | "_st_intersects_nband_geom" => {
7658            let serde_json::Value::Object(spec) = operand else {
7659                return Err(async_graphql::Error::new(format!(
7660                    "\"{}\" takes an object",
7661                    op
7662                )));
7663            };
7664            let Some(geometry) = spec.get("geommin") else {
7665                return Err(async_graphql::Error::new(format!(
7666                    "\"{}\" needs a shape",
7667                    op
7668                )));
7669            };
7670            let band = spec.get("nband").filter(|v| !v.is_null());
7671            let geometry_sql = shape(geometry, param_idx);
7672            Ok(match band {
7673                None => format!("{}({}, {})", function, quoted, geometry_sql),
7674                Some(band) => {
7675                    let placeholder = format!("${}", param_idx);
7676                    *param_idx += 1;
7677                    values.push(band.clone());
7678                    // `ST_Intersects(raster, nband, geometry)` is the spelling
7679                    // with a band; the argument order is PostGIS's, not the
7680                    // input's.
7681                    format!(
7682                        "{}({}, {}::int, {})",
7683                        function, quoted, placeholder, geometry_sql
7684                    )
7685                }
7686            })
7687        }
7688        // Everything else is the relation between this shape and one other.
7689        _ => {
7690            let other = shape(operand, param_idx);
7691            Ok(format!("{}({}, {})", function, quoted, other))
7692        }
7693    }
7694}
7695
7696/// A list of values as an array PostgreSQL will read.
7697///
7698/// Bound one element at a time rather than as one parameter. A JSON array
7699/// arrives as the text `["a","b"]`, which is not an array literal -- PostgreSQL
7700/// answers `malformed array literal` and means it.
7701fn sql_array(
7702    items: &[serde_json::Value],
7703    cast: &str,
7704    param_idx: &mut usize,
7705    values: &mut Vec<serde_json::Value>,
7706) -> String {
7707    if items.is_empty() {
7708        return format!("ARRAY[]::{}", cast);
7709    }
7710    let placeholders: Vec<String> = items
7711        .iter()
7712        .map(|item| {
7713            let placeholder = format!("${}", param_idx);
7714            *param_idx += 1;
7715            values.push(item.clone());
7716            placeholder
7717        })
7718        .collect();
7719    format!("ARRAY[{}]::{}", placeholders.join(", "), cast)
7720}
7721
7722/// One comparison against one column.
7723#[allow(clippy::too_many_arguments)]
7724fn comparison_sql(
7725    quoted: &str,
7726    column: &str,
7727    column_type: Option<&str>,
7728    op: &str,
7729    operand: &serde_json::Value,
7730    param_idx: &mut usize,
7731    values: &mut Vec<serde_json::Value>,
7732) -> Result<String, async_graphql::Error> {
7733    // `_cast` is not a comparison but a change of what is being compared: the
7734    // column becomes another type and the comparisons inside it apply to that.
7735    // A geometry and a geography answer different questions about the same
7736    // shape -- one on a plane, one on a sphere -- and this is how a client asks
7737    // for the other without the schema carrying two columns.
7738    if op == "_cast" {
7739        let serde_json::Value::Object(casts) = operand else {
7740            return Err(async_graphql::Error::new(
7741                "_cast takes an object naming the type to compare as",
7742            ));
7743        };
7744        let mut conditions = Vec::new();
7745        for (target, comparisons) in casts {
7746            // The GraphQL name of what is being compared as, and the
7747            // PostgreSQL type behind it. `String` is the one that differs:
7748            // a document compared as text is compared as `text`.
7749            let pg_type = match target.as_str() {
7750                "geometry" | "geography" => target.as_str(),
7751                "String" => "text",
7752                _ => {
7753                    return Err(async_graphql::Error::new(format!(
7754                        "cannot compare \"{}\" as \"{}\"",
7755                        column, target
7756                    )))
7757                }
7758            };
7759            let serde_json::Value::Object(ops) = comparisons else {
7760                continue;
7761            };
7762            let cast = format!("{}::{}", quoted, pg_type);
7763            for (nested_op, nested_operand) in ops {
7764                conditions.push(comparison_sql(
7765                    &cast,
7766                    column,
7767                    Some(pg_type),
7768                    nested_op,
7769                    nested_operand,
7770                    param_idx,
7771                    values,
7772                )?);
7773            }
7774        }
7775        return Ok(match conditions.len() {
7776            0 => "true".to_string(),
7777            1 => conditions.pop().expect("just counted"),
7778            _ => format!("({})", conditions.join(" AND ")),
7779        });
7780    }
7781
7782    // A spatial relation is a function of two shapes rather than an operator
7783    // between them, so it is written before the operator table is consulted.
7784    if let Some(function) = crate::input::bool_exp::postgis_function(op) {
7785        return postgis_sql(
7786            quoted,
7787            column_type,
7788            function,
7789            op,
7790            operand,
7791            param_idx,
7792            values,
7793        );
7794    }
7795
7796    // A tree comparison is an operator, but one whose operand has to be cast:
7797    // `?` is "any of these labels" for an ltree and "has this key" for a
7798    // jsonb, and PostgreSQL tells them apart by the operand's type alone.
7799    if let Some((operator, cast)) = crate::input::bool_exp::ltree_operator(op) {
7800        if cast.ends_with("[]") {
7801            let items = operand.as_array().ok_or_else(|| {
7802                async_graphql::Error::new(format!(
7803                    "the \"{}\" comparison on \"{}\" takes a list",
7804                    op, column
7805                ))
7806            })?;
7807            return Ok(format!(
7808                "{} {} {}",
7809                quoted,
7810                operator,
7811                sql_array(items, cast, param_idx, values)
7812            ));
7813        }
7814        let placeholder = format!("${}", param_idx);
7815        *param_idx += 1;
7816        values.push(operand.clone());
7817        return Ok(format!("{} {} {}::{}", quoted, operator, placeholder, cast));
7818    }
7819
7820    // The path language is a query over the document rather than a comparison
7821    // against one, so it is a function of the column and the path: `_exists`
7822    // asks whether the path selects anything, `_match` whether the predicate
7823    // it ends in holds.
7824    if let Some(function) = match op {
7825        "_jsonb_path_exists" => Some("jsonb_path_exists"),
7826        "_jsonb_path_match" => Some("jsonb_path_match"),
7827        _ => None,
7828    } {
7829        let placeholder = format!("${}", param_idx);
7830        *param_idx += 1;
7831        values.push(operand.clone());
7832        return Ok(format!(
7833            "{}({}, {}::jsonpath)",
7834            function, quoted, placeholder
7835        ));
7836    }
7837
7838    // Comparisons binding exactly one parameter, by the SQL they become.
7839    let binary = match op {
7840        "_eq" => Some("="),
7841        "_neq" => Some("<>"),
7842        "_gt" => Some(">"),
7843        "_gte" => Some(">="),
7844        "_lt" => Some("<"),
7845        "_lte" => Some("<="),
7846        "_like" => Some("LIKE"),
7847        "_nlike" => Some("NOT LIKE"),
7848        "_ilike" => Some("ILIKE"),
7849        "_nilike" => Some("NOT ILIKE"),
7850        "_similar" => Some("SIMILAR TO"),
7851        "_nsimilar" => Some("NOT SIMILAR TO"),
7852        "_regex" => Some("~"),
7853        "_nregex" => Some("!~"),
7854        "_iregex" => Some("~*"),
7855        "_niregex" => Some("!~*"),
7856        "_contains" => Some("@>"),
7857        "_contained_in" => Some("<@"),
7858        "_has_key" => Some("?"),
7859        _ => None,
7860    };
7861
7862    if let Some(sql_op) = binary {
7863        let placeholder = format!("${}", param_idx);
7864        *param_idx += 1;
7865        // A containment operand is a whole document, and `"latest"` is one --
7866        // a JSON string, which is not the same text as `latest`. Binding the
7867        // bare string answered `invalid input syntax for type json`, so what
7868        // goes over the wire is the value as it is written in JSON.
7869        values.push(match op {
7870            "_contains" | "_contained_in" => match operand {
7871                serde_json::Value::String(_)
7872                | serde_json::Value::Number(_)
7873                | serde_json::Value::Bool(_) => serde_json::Value::String(operand.to_string()),
7874                other => other.clone(),
7875            },
7876            _ => operand.clone(),
7877        });
7878        // A bound parameter arrives as text and PostgreSQL infers a type for
7879        // it from the operator, which works only while the inference is
7880        // unambiguous. `jsonb @> text` is not an operator at all; and `id =
7881        // $1` against an integer column answers `operator does not exist:
7882        // integer = text` the moment the value is one PostgreSQL cannot read
7883        // as a number -- a null, most obviously, which is how a client asks a
7884        // question it expects an empty answer to.
7885        //
7886        // So the operand says what it is. The containment and key operators
7887        // name the type the operator needs; everything else names the
7888        // column's, which is the type it is being compared against.
7889        let cast = match op {
7890            "_contains" | "_contained_in" => "::jsonb".to_string(),
7891            "_has_key" => "::text".to_string(),
7892            // Only where inference would otherwise pick text and be wrong. A
7893            // cast tells PostgreSQL what the *result* is, and it infers the
7894            // parameter feeding it as text -- so `$1::int4` bound with a
7895            // number sends binary int8 bytes into a text parameter and
7896            // answers `invalid byte sequence for encoding "UTF8"`. A value
7897            // PostgreSQL can read as a number needs no help; a null is the one
7898            // that does, because `id = $1` with nothing to infer from is
7899            // `integer = text`.
7900            _ if operand.is_null() => match column_type {
7901                Some(pg_type) => format!("::{}", pg_type),
7902                None => String::new(),
7903            },
7904            // A parameter arrives as `text`, and PostgreSQL then resolves the
7905            // operator by taking the *column* down to text where it can --
7906            // which is the wrong operator, and sometimes no operator at all.
7907            // `citext = text` compares case-sensitively against a
7908            // case-insensitive column; `uuid = text` does not exist. So a
7909            // written value names the type it is being compared against.
7910            //
7911            // Only a string: a number bound with a cast is sent as binary into
7912            // a parameter PostgreSQL has inferred as text, which is the
7913            // `invalid byte sequence for encoding "UTF8"` this used to answer.
7914            _ if operand.is_string() => match column_type {
7915                Some(pg_type) => format!("::{}", pg_type),
7916                None => String::new(),
7917            },
7918            _ => String::new(),
7919        };
7920        return Ok(format!("{} {} {}{}", quoted, sql_op, placeholder, cast));
7921    }
7922
7923    match op {
7924        "_is_null" => Ok(if operand.as_bool().unwrap_or(false) {
7925            format!("{} IS NULL", quoted)
7926        } else {
7927            format!("{} IS NOT NULL", quoted)
7928        }),
7929        "_in" | "_nin" => {
7930            let items = operand.as_array().ok_or_else(|| {
7931                async_graphql::Error::new(format!(
7932                    "the \"{}\" comparison on \"{}\" requires a list of values",
7933                    op, column
7934                ))
7935            })?;
7936            if items.is_empty() {
7937                // `IN ()` is not valid SQL. An empty `_in` matches nothing and
7938                // an empty `_nin` matches everything.
7939                return Ok(if op == "_in" { "false" } else { "true" }.to_string());
7940            }
7941            let mut placeholders = Vec::with_capacity(items.len());
7942            for item in items {
7943                // Cast only a null, for the reason the binary comparisons
7944                // give: a cast makes PostgreSQL infer the parameter as text.
7945                let cast = match (item.is_null() || item.is_string(), column_type) {
7946                    (true, Some(pg_type)) => format!("::{}", pg_type),
7947                    _ => String::new(),
7948                };
7949                placeholders.push(format!("${}{}", param_idx, cast));
7950                values.push(item.clone());
7951                *param_idx += 1;
7952            }
7953            Ok(format!(
7954                "{} {} ({})",
7955                quoted,
7956                if op == "_in" { "IN" } else { "NOT IN" },
7957                placeholders.join(", ")
7958            ))
7959        }
7960        "_has_keys_any" | "_has_keys_all" => {
7961            let items = operand.as_array().ok_or_else(|| {
7962                async_graphql::Error::new(format!(
7963                    "the \"{}\" comparison on \"{}\" requires a list of keys",
7964                    op, column
7965                ))
7966            })?;
7967            Ok(format!(
7968                "{} {} {}",
7969                quoted,
7970                if op == "_has_keys_any" { "?|" } else { "?&" },
7971                sql_array(items, "text[]", param_idx, values)
7972            ))
7973        }
7974        other => Err(async_graphql::Error::new(format!(
7975            "unsupported comparison \"{}\" on \"{}\"",
7976            other, column
7977        ))),
7978    }
7979}
7980
7981/// Build an `ORDER BY` clause from the `order_by` argument.
7982///
7983/// The argument is a list of single-column objects -- `[{name: asc}, {id:
7984/// desc}]` -- and the list is ordered, which is why it is a list and not one
7985/// object with several keys. Column names are checked against the table in the
7986/// schema cache before being quoted, so a name that is unknown, or crafted to
7987/// inject SQL, is rejected rather than interpolated. Returns an empty string
7988/// when no ordering was requested.
7989#[allow(clippy::too_many_arguments)] // one parameter per SQL clause
7990async fn build_order_by_clause(
7991    ctx: &ResolverContext<'_>,
7992    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
7993    schema_name: &str,
7994    table_name: &str,
7995    type_name: &str,
7996    relationships: &HashMap<String, Vec<RelationshipField>>,
7997    reference: &str,
7998    names: &crate::names::NameOverrides,
7999) -> Result<String, async_graphql::Error> {
8000    let Ok(order_arg) = ctx.args.try_get("order_by") else {
8001        return Ok(String::new());
8002    };
8003
8004    let value = accessor_to_json(&order_arg);
8005    // A single object is accepted as a one-entry list, which is what a client
8006    // writing `order_by: {name: asc}` means and what Hasura reads it as.
8007    let entries: Vec<&serde_json::Value> = match &value {
8008        serde_json::Value::Array(items) => items.iter().collect(),
8009        serde_json::Value::Object(_) => vec![&value],
8010        serde_json::Value::Null => return Ok(String::new()),
8011        _ => {
8012            return Err(async_graphql::Error::new(
8013                "order_by takes an object or a list of them",
8014            ))
8015        }
8016    };
8017    if entries.is_empty() {
8018        return Ok(String::new());
8019    }
8020
8021    let guard = schema_cache
8022        .get()
8023        .await
8024        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8025    let cache = guard
8026        .as_ref()
8027        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8028    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8029    let table = cache
8030        .get_table(&qi)
8031        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8032
8033    let mut terms = Vec::new();
8034    let mut alias_counter = 0usize;
8035    for entry in entries {
8036        order_terms_into(
8037            entry,
8038            cache,
8039            relationships,
8040            type_name,
8041            table,
8042            reference,
8043            names,
8044            &mut alias_counter,
8045            &mut terms,
8046        )?;
8047    }
8048
8049    if terms.is_empty() {
8050        return Ok(String::new());
8051    }
8052    Ok(format!(" ORDER BY {}", terms.join(", ")))
8053}
8054
8055/// Collect the `ORDER BY` terms one entry contributes.
8056///
8057/// A key whose value is a direction is a column of this table. A key whose
8058/// value is an object is something the row points at, and ordering by it is a
8059/// correlated subselect: one related row contributes its column, and many
8060/// contribute an aggregate. That is the whole difference between the two
8061/// sides, and PostgreSQL will take a scalar subquery anywhere a column goes.
8062#[allow(clippy::too_many_arguments)]
8063fn order_terms_into(
8064    entry: &serde_json::Value,
8065    cache: &SchemaCache,
8066    relationships: &HashMap<String, Vec<RelationshipField>>,
8067    type_name: &str,
8068    table: &postrust_core::schema_cache::Table,
8069    reference: &str,
8070    names: &crate::names::NameOverrides,
8071    alias_counter: &mut usize,
8072    terms: &mut Vec<String>,
8073) -> Result<(), async_graphql::Error> {
8074    let serde_json::Value::Object(map) = entry else {
8075        return Err(async_graphql::Error::new(
8076            "each order_by entry is an object mapping a column to a direction",
8077        ));
8078    };
8079    let available: &[RelationshipField] = relationships
8080        .get(type_name)
8081        .map(|r| r.as_slice())
8082        .unwrap_or(&[]);
8083
8084    for (key, value) in map {
8085        // A direction given as null is no direction. `order_by: {id:
8086        // $direction}` with nothing bound for `$direction` is how a client
8087        // makes an ordering optional without writing the query twice, and the
8088        // same goes for `{author: $author_order_by}`, which is a whole
8089        // ordering left out.
8090        if value.is_null() {
8091            continue;
8092        }
8093
8094        // A direction: this table's own column, or a function of its row.
8095        if let Some(name) = value.as_str() {
8096            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8097                async_graphql::Error::new(format!(
8098                    "\"{}\" is not a sort direction; expected one of asc, desc, \
8099                     asc_nulls_first, asc_nulls_last, desc_nulls_first, desc_nulls_last",
8100                    name
8101                ))
8102            })?;
8103            let column = table_column_for(names, table, key);
8104            if table.get_column(column).is_some() {
8105                terms.push(format!(
8106                    "{}.{} {}",
8107                    reference,
8108                    postrust_sql::escape_ident(column),
8109                    sql
8110                ));
8111                continue;
8112            }
8113            // A computed field is a column as far as ordering is concerned:
8114            // the row is the argument, and the call is what is sorted on.
8115            let function = names
8116                .computed_source(&table.schema, &table.name, key)
8117                .unwrap_or(key);
8118            if let Some(definition) = table.get_computed_column(function) {
8119                terms.push(format!(
8120                    "{} {}",
8121                    computed_call(definition, &format!("{}.*", reference), reference),
8122                    sql
8123                ));
8124                continue;
8125            }
8126            return Err(async_graphql::Error::new(format!(
8127                "cannot order by unknown column \"{}\" on \"{}\"",
8128                key, table.name
8129            )));
8130        }
8131
8132        // An aggregate of the rows that point here.
8133        if let Some(rel) = key
8134            .strip_suffix("_aggregate")
8135            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list))
8136        {
8137            aggregate_order_terms(value, rel, cache, reference, alias_counter, terms)?;
8138            continue;
8139        }
8140
8141        // A column of the row this one points at.
8142        if let Some(rel) = available.iter().find(|r| r.name == *key && !r.is_list) {
8143            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8144                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8145            if plan.columns.is_empty() {
8146                return Err(async_graphql::Error::new(format!(
8147                    "cannot order by \"{}\": it is not reached by a key",
8148                    key
8149                )));
8150            }
8151            *alias_counter += 1;
8152            let alias = format!("pgrst_ord_{}", alias_counter);
8153            let quoted_alias = postrust_sql::escape_ident(&alias);
8154            let correlation = plan
8155                .columns
8156                .iter()
8157                .map(|(local, foreign)| {
8158                    format!(
8159                        "{} = {}.{}",
8160                        format_args!("{}.{}", reference, postrust_sql::escape_ident(local)),
8161                        quoted_alias,
8162                        postrust_sql::escape_ident(foreign)
8163                    )
8164                })
8165                .collect::<Vec<_>>()
8166                .join(" AND ");
8167
8168            let target_qi = postrust_core::api_request::QualifiedIdentifier::new(
8169                &plan.foreign_schema,
8170                &plan.foreign_table,
8171            );
8172            let Some(target) = cache.get_table(&target_qi) else {
8173                return Err(async_graphql::Error::new(format!(
8174                    "unknown table \"{}\"",
8175                    plan.foreign_table
8176                )));
8177            };
8178
8179            // The related row's own terms, then wrapped one at a time: a
8180            // subquery yields one value, so each term is its own subselect.
8181            let mut nested = Vec::new();
8182            order_terms_into(
8183                value,
8184                cache,
8185                relationships,
8186                &rel.target_type,
8187                target,
8188                &quoted_alias,
8189                names,
8190                alias_counter,
8191                &mut nested,
8192            )?;
8193            for term in nested {
8194                let (expression, direction) = term
8195                    .rsplit_once(' ')
8196                    .map(|(e, d)| (e.to_string(), d.to_string()))
8197                    .unwrap_or((term.clone(), String::new()));
8198                terms.push(format!(
8199                    "(SELECT {} FROM {}.{} AS {} WHERE {}) {}",
8200                    expression,
8201                    postrust_sql::escape_ident(&plan.foreign_schema),
8202                    postrust_sql::escape_ident(&plan.foreign_table),
8203                    quoted_alias,
8204                    correlation,
8205                    direction
8206                ));
8207            }
8208            continue;
8209        }
8210
8211        return Err(async_graphql::Error::new(format!(
8212            "cannot order by \"{}\" on \"{}\"",
8213            key, table.name
8214        )));
8215    }
8216    Ok(())
8217}
8218
8219/// The `ORDER BY` terms for an aggregate of a row's children.
8220fn aggregate_order_terms(
8221    value: &serde_json::Value,
8222    rel: &RelationshipField,
8223    cache: &SchemaCache,
8224    reference: &str,
8225    alias_counter: &mut usize,
8226    terms: &mut Vec<String>,
8227) -> Result<(), async_graphql::Error> {
8228    let serde_json::Value::Object(spec) = value else {
8229        return Err(async_graphql::Error::new(
8230            "ordering by an aggregate takes an object, such as {count: desc}",
8231        ));
8232    };
8233    let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8234        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8235    if plan.columns.is_empty() && plan.function.is_none() {
8236        return Err(async_graphql::Error::new(format!(
8237            "cannot order by an aggregate of \"{}\": it is not reached by a key",
8238            rel.name
8239        )));
8240    }
8241    // A computed relationship whose function takes more than the row cannot be
8242    // ordered by here: there is nowhere in `order_by` to write the arguments.
8243    if plan.function.is_some() && !rel.arguments.is_empty() {
8244        return Err(async_graphql::Error::new(format!(
8245            "cannot order by an aggregate of \"{}\": it takes arguments, and an \
8246             ordering has nowhere to write them",
8247            rel.name
8248        )));
8249    }
8250
8251    *alias_counter += 1;
8252    let alias = postrust_sql::escape_ident(&format!("pgrst_ord_{}", alias_counter));
8253    // A computed relationship is correlated by argument -- the function takes
8254    // the parent row -- where a key relationship is correlated by columns.
8255    let (source, correlation) = match &plan.function {
8256        Some(function) => {
8257            let session = plan.row_argument.as_ref().and_then(|row_argument| {
8258                computed_session_argument(cache, function, row_argument)
8259                    .map(|session| (row_argument.clone(), session))
8260            });
8261            let call = match session {
8262                Some((row_argument, session)) => format!(
8263                    "{} => {}, {} => {}",
8264                    postrust_sql::escape_ident(&row_argument),
8265                    reference,
8266                    postrust_sql::escape_ident(&session),
8267                    SESSION_ARGUMENT
8268                ),
8269                None => reference.to_string(),
8270            };
8271            (
8272                format!(
8273                    "{}.{}({})",
8274                    postrust_sql::escape_ident(&function.schema),
8275                    postrust_sql::escape_ident(&function.name),
8276                    call
8277                ),
8278                "true".to_string(),
8279            )
8280        }
8281        None => (
8282            format!(
8283                "{}.{}",
8284                postrust_sql::escape_ident(&plan.foreign_schema),
8285                postrust_sql::escape_ident(&plan.foreign_table)
8286            ),
8287            plan.columns
8288                .iter()
8289                .map(|(local, foreign)| {
8290                    format!(
8291                        "{}.{} = {}.{}",
8292                        reference,
8293                        postrust_sql::escape_ident(local),
8294                        alias,
8295                        postrust_sql::escape_ident(foreign)
8296                    )
8297                })
8298                .collect::<Vec<_>>()
8299                .join(" AND "),
8300        ),
8301    };
8302
8303    // `{count: desc}` is one term; `{max: {id: desc}}` is one per column.
8304    let mut wanted: Vec<(String, String)> = Vec::new();
8305    for (function, argument) in spec {
8306        // A direction given as null is no direction, as everywhere else an
8307        // ordering is read.
8308        if argument.is_null() {
8309            continue;
8310        }
8311        if function == "count" {
8312            let name = argument.as_str().unwrap_or_default();
8313            let Some(direction) = crate::input::order_by::direction_sql(name) else {
8314                return Err(async_graphql::Error::new(format!(
8315                    "\"{}\" is not a sort direction",
8316                    name
8317                )));
8318            };
8319            wanted.push(("count(*)".to_string(), direction.to_string()));
8320            continue;
8321        }
8322        let serde_json::Value::Object(columns) = argument else {
8323            continue;
8324        };
8325        for (column, direction) in columns {
8326            if direction.is_null() {
8327                continue;
8328            }
8329            let name = direction.as_str().unwrap_or_default();
8330            let Some(direction) = crate::input::order_by::direction_sql(name) else {
8331                return Err(async_graphql::Error::new(format!(
8332                    "\"{}\" is not a sort direction",
8333                    name
8334                )));
8335            };
8336            // The function comes from the generated input, not from the
8337            // request: a client can only name one that was offered.
8338            wanted.push((
8339                format!(
8340                    "{}({}.{})",
8341                    function,
8342                    alias,
8343                    postrust_sql::escape_ident(column)
8344                ),
8345                direction.to_string(),
8346            ));
8347        }
8348    }
8349
8350    for (expression, direction) in wanted {
8351        terms.push(format!(
8352            "(SELECT {} FROM {} AS {} WHERE {}) {}",
8353            expression, source, alias, correlation, direction
8354        ));
8355    }
8356    Ok(())
8357}
8358
8359/// The `DISTINCT ON (...)` prefix, and the ordering that has to go with it.
8360///
8361/// PostgreSQL keeps the first row of each DISTINCT ON group in the query's own
8362/// order, and picks arbitrarily where the ordering does not begin with the
8363/// distinct columns -- so which row survives would depend on the plan. Hasura
8364/// refuses that query rather than answering it, and so does this: prepending
8365/// the distinct columns instead produced an answer, and a wrong one, since
8366/// `ORDER BY "department", "department" DESC` is decided by its first term and
8367/// sorts ascending. Where nothing was ordered at all there is nothing to
8368/// disagree with, so the distinct columns become the ordering.
8369///
8370/// `written` is the ordering as its comma-separated terms, without the
8371/// keyword; the answer is in the same spelling.
8372fn distinct_on_clause(
8373    distinct_on: &[String],
8374    written: Option<&str>,
8375) -> Result<(String, Option<String>), async_graphql::Error> {
8376    if distinct_on.is_empty() {
8377        return Ok((String::new(), written.map(str::to_string)));
8378    }
8379    let terms: Vec<&str> = written
8380        .map(|written| written.split(", ").collect())
8381        .unwrap_or_default();
8382    // Terms are qualified and the distinct columns may not be, so they are
8383    // compared by the name itself.
8384    let column_of = |term: &str| {
8385        let expression = term.split_whitespace().next().unwrap_or(term);
8386        expression
8387            .rsplit_once('.')
8388            .map(|(_, name)| name)
8389            .unwrap_or(expression)
8390            .to_string()
8391    };
8392    let leading: Vec<String> = terms
8393        .iter()
8394        .take(distinct_on.len())
8395        .map(|term| column_of(term))
8396        .collect();
8397    if !terms.is_empty()
8398        && (leading.len() != distinct_on.len()
8399            || distinct_on
8400                .iter()
8401                .any(|column| !leading.contains(&column_of(column))))
8402    {
8403        return Err(validation_error(
8404            "\"distinct_on\" columns must match initial \"order_by\" columns",
8405        ));
8406    }
8407    let order = match terms.is_empty() {
8408        true => Some(distinct_on.join(", ")),
8409        false => written.map(str::to_string),
8410    };
8411    Ok((format!("DISTINCT ON ({}) ", distinct_on.join(", ")), order))
8412}
8413
8414/// Build the `DISTINCT ON (...)` prefix from the `distinct_on` argument.
8415///
8416/// PostgreSQL requires the leftmost `ORDER BY` terms to match the distinct
8417/// columns, and picks an arbitrary surviving row otherwise. The caller
8418/// prepends these terms to whatever ordering was asked for rather than
8419/// replacing it, so `distinct_on: [name]` with `order_by: [{id: desc}]` keeps
8420/// the highest id per name instead of an unpredictable one.
8421async fn build_distinct_on(
8422    ctx: &ResolverContext<'_>,
8423    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8424    schema_name: &str,
8425    table_name: &str,
8426    names: &crate::names::NameOverrides,
8427) -> Result<Vec<String>, async_graphql::Error> {
8428    let Ok(arg) = ctx.args.try_get("distinct_on") else {
8429        return Ok(Vec::new());
8430    };
8431    let value = accessor_to_json(&arg);
8432    let requested: Vec<String> = match &value {
8433        serde_json::Value::Array(items) => items
8434            .iter()
8435            .filter_map(|i| i.as_str().map(|s| s.to_string()))
8436            .collect(),
8437        serde_json::Value::String(one) => vec![one.clone()],
8438        _ => Vec::new(),
8439    };
8440    if requested.is_empty() {
8441        return Ok(Vec::new());
8442    }
8443
8444    let guard = schema_cache
8445        .get()
8446        .await
8447        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8448    let cache = guard
8449        .as_ref()
8450        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8451    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8452    let table = cache
8453        .get_table(&qi)
8454        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8455
8456    let mut quoted = Vec::with_capacity(requested.len());
8457    for field in requested {
8458        let name = table_column_for(names, table, &field).to_string();
8459        if table.get_column(&name).is_none() {
8460            return Err(async_graphql::Error::new(format!(
8461                "cannot take distinct on unknown column \"{}\" of \"{}\"",
8462                field, table_name
8463            )));
8464        }
8465        quoted.push(postrust_sql::escape_ident(&name));
8466    }
8467    Ok(quoted)
8468}
8469
8470/// Build the SELECT-list expressions that embed relationships in one query.
8471///
8472/// The GraphQL mirror of the REST builder: each requested relationship becomes a
8473/// correlated subselect yielding JSON, so the whole selection comes back from
8474/// the parent query instead of one query per relationship per level.
8475/// The SELECT-list entries for any computed columns the selection asks for.
8476///
8477/// A computed column is a function of one argument of the table's own row
8478/// type, so it is not in `*` and has to be named. PostgreSQL's functional
8479/// notation reads `upper_name(author.*)` and `author.upper_name` as the same
8480/// call; the explicit form is written here because it says which function is
8481/// being called.
8482#[allow(clippy::too_many_arguments)] // the fields, their source, and the binding state
8483fn computed_projections<'a>(
8484    table: &postrust_core::schema_cache::Table,
8485    fields: impl IntoIterator<Item = SelectionField<'a>>,
8486    row_reference: &str,
8487    names: &crate::names::NameOverrides,
8488    schema_cache: &SchemaCache,
8489    param_idx: &mut usize,
8490    values: &mut Vec<serde_json::Value>,
8491) -> Result<Vec<String>, async_graphql::Error> {
8492    let mut projections = Vec::new();
8493    for field in fields {
8494        let name = field.name();
8495        // A real column wins, and the projection already carries it -- under
8496        // this name, whether or not that is the column's own.
8497        if table
8498            .get_column(table_column_for(names, table, name))
8499            .is_some()
8500        {
8501            continue;
8502        }
8503        // The field may be exposed under a name that was given rather than
8504        // the function's own, so the call is looked up from either side.
8505        let function = names
8506            .computed_source(&table.schema, &table.name, name)
8507            .unwrap_or(name);
8508        let Some(computed) = table.get_computed_column(function) else {
8509            continue;
8510        };
8511        let arguments = crate::schema::computed_caller_arguments(computed, schema_cache);
8512        let given = match arguments.is_empty() {
8513            true => None,
8514            false => embed_arguments(field).remove("args"),
8515        };
8516        projections.push(format!(
8517            "{} AS {}",
8518            computed_column_call(
8519                computed,
8520                row_reference,
8521                name,
8522                &arguments,
8523                given.as_ref(),
8524                param_idx,
8525                values,
8526            )?,
8527            postrust_sql::escape_ident(name)
8528        ));
8529    }
8530    Ok(projections)
8531}
8532
8533/// The call behind a computed field, including whatever the caller passed.
8534///
8535/// The ordinary case is a function of the row alone, which is written
8536/// positionally: that is the call REST writes, and the one a client that never
8537/// heard of `args` is asking for. Anything else has to be written by name --
8538/// the row is then not the only parameter and may not be the first -- so the
8539/// row, the session where the function asks for one, and each argument the
8540/// caller gave are all named.
8541fn computed_column_call(
8542    computed: &postrust_core::schema_cache::ComputedColumn,
8543    row: &str,
8544    field_name: &str,
8545    arguments: &[(String, String, bool)],
8546    given: Option<&serde_json::Value>,
8547    param_idx: &mut usize,
8548    values: &mut Vec<serde_json::Value>,
8549) -> Result<String, async_graphql::Error> {
8550    if arguments.is_empty() {
8551        return Ok(computed_call(computed, row, row));
8552    }
8553    let Some(row_argument) = &computed.row_argument else {
8554        return Ok(computed_call(computed, row, row));
8555    };
8556    let function = format!(
8557        "{}.{}",
8558        postrust_sql::escape_ident(&computed.function.schema),
8559        postrust_sql::escape_ident(&computed.function.name)
8560    );
8561    let mut passed = vec![format!(
8562        "{} => {}",
8563        postrust_sql::escape_ident(row_argument),
8564        row
8565    )];
8566    if let Some(session) = &computed.session_argument {
8567        passed.push(format!(
8568            "{} => {}",
8569            postrust_sql::escape_ident(session),
8570            SESSION_ARGUMENT
8571        ));
8572    }
8573    for (name, pg_type, required) in arguments {
8574        match given.and_then(|given| given.get(name)) {
8575            None if *required => {
8576                return Err(async_graphql::Error::new(format!(
8577                    "{} needs the argument \"{}\"",
8578                    field_name, name
8579                )))
8580            }
8581            // Left out is left out, so the function's own default applies.
8582            None => continue,
8583            Some(value) => {
8584                passed.push(format!(
8585                    "{} => ${}::{}",
8586                    postrust_sql::escape_ident(name),
8587                    param_idx,
8588                    pg_type
8589                ));
8590                *param_idx += 1;
8591                values.push(value.clone());
8592            }
8593        }
8594    }
8595    Ok(format!("{}({})", function, passed.join(", ")))
8596}
8597
8598/// The `count(...)` a selection asked for.
8599///
8600/// `count` alone is `count(*)`, which counts rows. `count(columns: [a])` is
8601/// `count("a")`, which counts the rows where `a` is not null -- a different
8602/// number, and the one the client asked for. `distinct: true` counts the
8603/// distinct values among them, and several columns are counted as the row
8604/// they make, which is how PostgreSQL counts more than one thing at once.
8605///
8606/// The column names come from the generated enum, so a selection can only
8607/// name a column the table has; what it may not know is the column's own name
8608/// where the schema exposes it under another.
8609fn count_expression(
8610    field: async_graphql::SelectionField<'_>,
8611    table: (&str, &str),
8612    alias: Option<&str>,
8613    names: &crate::names::NameOverrides,
8614) -> String {
8615    let arguments = embed_arguments(field);
8616    let requested = match arguments.get("columns") {
8617        Some(serde_json::Value::Array(items)) => items
8618            .iter()
8619            .filter_map(|item| item.as_str())
8620            .collect::<Vec<_>>(),
8621        Some(serde_json::Value::String(one)) => vec![one.as_str()],
8622        _ => Vec::new(),
8623    };
8624    if requested.is_empty() {
8625        return "count(*)".to_string();
8626    }
8627    let columns: Vec<String> = requested
8628        .into_iter()
8629        .map(|column| {
8630            let (schema, name) = table;
8631            let source = column_for(names, schema, name, column);
8632            match alias {
8633                Some(alias) => format!(
8634                    "{}.{}",
8635                    postrust_sql::escape_ident(alias),
8636                    postrust_sql::escape_ident(source)
8637                ),
8638                None => postrust_sql::escape_ident(source),
8639            }
8640        })
8641        .collect();
8642    // One column counts itself; several count the row they make, since
8643    // `count(a, b)` is not a call PostgreSQL has.
8644    let counted = match columns.len() {
8645        1 => columns.into_iter().next().unwrap_or_default(),
8646        _ => format!("({})", columns.join(", ")),
8647    };
8648    let distinct = matches!(
8649        arguments.get("distinct"),
8650        Some(serde_json::Value::Bool(true))
8651    );
8652    match distinct {
8653        true => format!("count(DISTINCT {})", counted),
8654        false => format!("count({})", counted),
8655    }
8656}
8657
8658/// The SELECT list for a nested aggregate.
8659///
8660/// `articles_aggregate { aggregate { count } nodes { title } }` becomes one
8661/// row per parent, correlated the way any embed is. Both halves are aggregates
8662/// over the same correlated set, so they go in one select list rather than two
8663/// queries -- `count(*)` and `json_agg(...)` read the same rows.
8664#[allow(clippy::too_many_arguments)] // the recursion carries its whole context
8665fn nested_aggregate_select(
8666    caller: &crate::role::Caller<'_>,
8667    selection: async_graphql::SelectionField<'_>,
8668    child_alias: &str,
8669    schema_cache: &SchemaCache,
8670    relationships: &HashMap<String, Vec<RelationshipField>>,
8671    target_type: &str,
8672    child_table: Option<&postrust_core::schema_cache::Table>,
8673    max_rows: Option<i64>,
8674    alias_counter: &mut usize,
8675    param_idx: &mut usize,
8676    values: &mut Vec<serde_json::Value>,
8677    names: &crate::names::NameOverrides,
8678) -> Result<String, async_graphql::Error> {
8679    use crate::schema::aggregate as agg;
8680
8681    let mut parts: Vec<String> = Vec::new();
8682
8683    for child in selection.selection_set() {
8684        match child.name() {
8685            "aggregate" => {
8686                let of_table = child_table
8687                    .map(|table| (table.schema.as_str(), table.name.as_str()))
8688                    .unwrap_or(("", ""));
8689                // Each `count` is answered under the name it was asked for,
8690                // since two of them may count different things. Every other
8691                // aggregate is one object read under however many names, so
8692                // its columns are the union of what all of them named.
8693                let mut counts: Vec<(String, String)> = Vec::new();
8694                let mut wanted: BTreeMap<&str, Vec<String>> = BTreeMap::new();
8695                for function in child.selection_set() {
8696                    if function.name() == "count" {
8697                        counts.push((
8698                            function.alias().unwrap_or("count").to_string(),
8699                            count_expression(function, of_table, Some(child_alias), names),
8700                        ));
8701                        continue;
8702                    }
8703                    let sql_function = agg::NUMERIC_AGGREGATES
8704                        .iter()
8705                        .chain(agg::ORDERED_AGGREGATES.iter())
8706                        .find(|(name, _)| *name == function.name())
8707                        .map(|(name, _)| *name);
8708                    let Some(sql_function) = sql_function else {
8709                        continue;
8710                    };
8711                    let columns = wanted.entry(sql_function).or_default();
8712                    for column in function.selection_set() {
8713                        let name = column.name().to_string();
8714                        // Answered by the schema, not a column.
8715                        if name != "__typename" && !columns.contains(&name) {
8716                            columns.push(name);
8717                        }
8718                    }
8719                }
8720                // Answered whether or not it was asked for, for the reason
8721                // the root aggregate answers it.
8722                let mut fields = match counts.iter().any(|(key, _)| key == "count") {
8723                    true => Vec::new(),
8724                    false => vec!["'count', count(*)".to_string()],
8725                };
8726                for (key, expression) in &counts {
8727                    fields.push(format!("'{}', {}", key.replace('\'', "''"), expression));
8728                }
8729                for (sql_function, named) in &wanted {
8730                    let columns: Vec<String> = named
8731                        .iter()
8732                        .map(|column| {
8733                            let source = child_table
8734                                .map(|table| table_column_for(names, table, column))
8735                                .unwrap_or(column.as_str());
8736                            format!(
8737                                "'{}', {}({}.{})",
8738                                column.replace('\'', "''"),
8739                                sql_function,
8740                                postrust_sql::escape_ident(child_alias),
8741                                postrust_sql::escape_ident(source)
8742                            )
8743                        })
8744                        .collect();
8745                    // Pushed even when empty, as at the root: `max { __typename }`
8746                    // is an object with nothing in it, not a null.
8747                    fields.push(format!(
8748                        "'{}', json_build_object({})",
8749                        sql_function,
8750                        columns.join(", ")
8751                    ));
8752                }
8753                parts.push(format!(
8754                    "json_build_object({}) AS {}",
8755                    fields.join(", "),
8756                    postrust_sql::escape_ident("aggregate")
8757                ));
8758            }
8759            // `nodes` under an aggregate is a rows selection like any other:
8760            // what it names may be a column, a computed field, or a
8761            // relationship of its own, and the last of those is a further
8762            // correlated subselect rather than a column of this one.
8763            "nodes" => {
8764                let embeds = build_embed_expressions(
8765                    caller,
8766                    schema_cache,
8767                    relationships,
8768                    target_type,
8769                    child_alias,
8770                    child.selection_set(),
8771                    max_rows,
8772                    alias_counter,
8773                    param_idx,
8774                    values,
8775                    names,
8776                )?;
8777                let mut columns: Vec<String> = Vec::new();
8778                for column in child.selection_set() {
8779                    let name = column.name();
8780                    if name == "__typename" || embeds.iter().any(|(field, _)| field == name) {
8781                        continue;
8782                    }
8783                    columns.push(format!(
8784                        "'{}', {}",
8785                        name.replace('\'', "''"),
8786                        column_expression(child_table, child_alias, name, names)
8787                    ));
8788                }
8789                for (field, expression) in &embeds {
8790                    columns.push(format!("'{}', {}", field.replace('\'', "''"), expression));
8791                }
8792                let row = if columns.is_empty() {
8793                    format!("row_to_json({})", postrust_sql::escape_ident(child_alias))
8794                } else {
8795                    format!("json_build_object({})", columns.join(", "))
8796                };
8797                parts.push(format!(
8798                    "COALESCE(json_agg({}), '[]'::json) AS {}",
8799                    row,
8800                    postrust_sql::escape_ident("nodes")
8801                ));
8802            }
8803            _ => {}
8804        }
8805    }
8806
8807    if parts.is_empty() {
8808        // A selection of nothing but `__typename`. One row, no columns, is not
8809        // valid SQL; a count nobody reads is.
8810        parts.push("count(*) AS pgrst_empty".to_string());
8811    }
8812    Ok(parts.join(", "))
8813}
8814
8815/// How one named field of a row is read, given the alias the row goes by.
8816///
8817/// A column is itself; a computed field is the call that produces it. Which of
8818/// the two a name is depends on the table, so this is the one place that asks.
8819fn column_expression(
8820    table: Option<&postrust_core::schema_cache::Table>,
8821    alias: &str,
8822    name: &str,
8823    names: &crate::names::NameOverrides,
8824) -> String {
8825    let column = table
8826        .map(|t| table_column_for(names, t, name))
8827        .unwrap_or(name);
8828    let plain = format!(
8829        "{}.{}",
8830        postrust_sql::escape_ident(alias),
8831        postrust_sql::escape_ident(column)
8832    );
8833    let computed = table
8834        .filter(|t| t.get_column(column).is_none())
8835        .and_then(|t| {
8836            let function = names
8837                .computed_source(&t.schema, &t.name, name)
8838                .unwrap_or(name);
8839            t.get_computed_column(function)
8840        });
8841    match computed {
8842        Some(definition) => computed_call(
8843            definition,
8844            &format!("{}.*", postrust_sql::escape_ident(alias)),
8845            &postrust_sql::escape_ident(alias),
8846        ),
8847        None => plain,
8848    }
8849}
8850
8851/// Render `order_by` terms against a table, qualified with an alias.
8852///
8853/// The root field's ordering reads its argument from the resolver context and
8854/// needs the schema cache asynchronously; an embed already holds both, so this
8855/// takes the value directly. Columns are checked against the table before they
8856/// are quoted, so an unknown or crafted name is refused rather than
8857/// interpolated.
8858fn order_terms(
8859    order: &serde_json::Value,
8860    schema_cache: &SchemaCache,
8861    schema_name: &str,
8862    table_name: &str,
8863    alias: &str,
8864    names: &crate::names::NameOverrides,
8865) -> Result<Option<String>, async_graphql::Error> {
8866    let entries: Vec<&serde_json::Value> = match order {
8867        serde_json::Value::Array(items) => items.iter().collect(),
8868        serde_json::Value::Object(_) => vec![order],
8869        _ => return Ok(None),
8870    };
8871
8872    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8873    let table = schema_cache
8874        .get_table(&qi)
8875        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8876
8877    let mut terms = Vec::new();
8878    for entry in entries {
8879        let serde_json::Value::Object(map) = entry else {
8880            continue;
8881        };
8882        for (field, direction) in map {
8883            // A direction given as null is no direction, as at the root.
8884            if direction.is_null() {
8885                continue;
8886            }
8887            let column = table_column_for(names, table, field);
8888            if table.get_column(column).is_none() {
8889                return Err(async_graphql::Error::new(format!(
8890                    "cannot order by unknown column \"{}\" on \"{}\"",
8891                    field, table_name
8892                )));
8893            }
8894            let name = direction.as_str().unwrap_or_default();
8895            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8896                async_graphql::Error::new(format!("\"{}\" is not a sort direction", name))
8897            })?;
8898            terms.push(format!(
8899                "{}.{} {}",
8900                postrust_sql::escape_ident(alias),
8901                postrust_sql::escape_ident(column),
8902                sql
8903            ));
8904        }
8905    }
8906
8907    Ok(if terms.is_empty() {
8908        None
8909    } else {
8910        Some(terms.join(", "))
8911    })
8912}
8913
8914/// Whether a to-one relationship always has a row at the other end.
8915///
8916/// It does when this row's own key columns cannot be null: a foreign key
8917/// guarantees a matching row for every value, so a `NOT NULL` key is a
8918/// relationship that cannot be absent, and the field is non-null. A client
8919/// generating types then gets `author` rather than `author | null`, which is
8920/// what the schema actually promises.
8921///
8922/// Only where a foreign key says so. A computed relationship is a function
8923/// call, and a one-to-one reached through the *child's* key is a row that may
8924/// simply not exist.
8925fn always_present(rel: &RelationshipField, table: &postrust_core::schema_cache::Table) -> bool {
8926    use postrust_core::schema_cache::{Cardinality, Relationship};
8927    let Relationship::ForeignKey { cardinality, .. } = &rel.relationship else {
8928        return false;
8929    };
8930    match cardinality {
8931        Cardinality::M2O { .. } => {}
8932        Cardinality::O2O {
8933            is_parent: false, ..
8934        } => {}
8935        _ => return false,
8936    }
8937    let columns = cardinality.columns();
8938    !columns.is_empty()
8939        && columns.iter().all(|(local, _)| {
8940            table
8941                .get_column(local)
8942                .is_some_and(|column| !column.nullable)
8943        })
8944}
8945
8946/// Whether the row at the other end of a relationship carries the key.
8947///
8948/// If it does, this row is written first and its key is pushed down; if this
8949/// row's own column carries it, the other row is written first and its key is
8950/// read back. Cardinality alone does not answer it: a one-to-one is one row in
8951/// either direction, and which side holds the foreign key is the whole
8952/// difference between the two orders.
8953fn child_holds_the_key(relationship: &postrust_core::schema_cache::Relationship) -> bool {
8954    use postrust_core::schema_cache::{Cardinality, Relationship};
8955    match relationship {
8956        Relationship::ForeignKey { cardinality, .. } => match cardinality {
8957            Cardinality::M2O { .. } => false,
8958            Cardinality::O2O { is_parent, .. } => *is_parent,
8959            _ => true,
8960        },
8961        Relationship::Computed { .. } => true,
8962    }
8963}
8964
8965/// The name of a function's session parameter, if it has one.
8966fn computed_session_argument(
8967    cache: &SchemaCache,
8968    function: &postrust_core::api_request::QualifiedIdentifier,
8969    row_argument: &str,
8970) -> Option<String> {
8971    let _ = row_argument;
8972    cache
8973        .routines
8974        .get(function)
8975        .into_iter()
8976        .flatten()
8977        .find(|routine| routine.name == function.name)?
8978        .params
8979        .iter()
8980        .find(|param| {
8981            param.name == "hasura_session" && matches!(param.param_type.as_str(), "json" | "jsonb")
8982        })
8983        .map(|param| param.name.clone())
8984}
8985
8986/// The name of the input holding a computed relationship's own arguments.
8987fn computed_args_type_name(type_name: &str, field: &str) -> String {
8988    format!("{}_{}_args", type_name, field)
8989}
8990
8991/// The argument list a computed relationship's function is called with.
8992///
8993/// `None` where the parent row is the only thing it takes -- the ordinary case,
8994/// and the positional call the REST surface writes. Anything else is written by
8995/// name: the row, the session where the function asks for one, and whatever the
8996/// client put under `args`.
8997fn computed_arguments(
8998    relationship: &RelationshipField,
8999    plan: &postrust_core::embed::EmbedPlan,
9000    parent_row: &str,
9001    given: Option<&serde_json::Value>,
9002    param_idx: &mut usize,
9003    values: &mut Vec<serde_json::Value>,
9004    schema_cache: &SchemaCache,
9005) -> Result<Option<String>, async_graphql::Error> {
9006    let (Some(function), Some(row_argument)) = (&plan.function, &plan.row_argument) else {
9007        return Ok(None);
9008    };
9009    let session = schema_cache
9010        .routines
9011        .get(function)
9012        .into_iter()
9013        .flatten()
9014        .find(|routine| routine.name == function.name)
9015        .and_then(|routine| {
9016            routine
9017                .params
9018                .iter()
9019                .find(|param| {
9020                    param.name == "hasura_session"
9021                        && matches!(param.param_type.as_str(), "json" | "jsonb")
9022                })
9023                .map(|param| param.name.clone())
9024        });
9025    if relationship.arguments.is_empty() && session.is_none() {
9026        return Ok(None);
9027    }
9028
9029    let mut passed = vec![format!(
9030        "{} => {}",
9031        postrust_sql::escape_ident(row_argument),
9032        parent_row
9033    )];
9034    if let Some(session) = session {
9035        passed.push(format!(
9036            "{} => {}",
9037            postrust_sql::escape_ident(&session),
9038            SESSION_ARGUMENT
9039        ));
9040    }
9041    for (name, pg_type, required) in &relationship.arguments {
9042        match given.and_then(|given| given.get(name)) {
9043            None if *required => {
9044                return Err(async_graphql::Error::new(format!(
9045                    "{} needs the argument \"{}\"",
9046                    relationship.name, name
9047                )))
9048            }
9049            // Left out is left out, so the function's own default applies.
9050            None => continue,
9051            Some(value) => {
9052                passed.push(format!(
9053                    "{} => ${}::{}",
9054                    postrust_sql::escape_ident(name),
9055                    param_idx,
9056                    pg_type
9057                ));
9058                *param_idx += 1;
9059                values.push(value.clone());
9060            }
9061        }
9062    }
9063    Ok(Some(passed.join(", ")))
9064}
9065
9066/// The arguments written on an embedded field, as JSON.
9067fn embed_arguments(field: async_graphql::SelectionField<'_>) -> HashMap<String, serde_json::Value> {
9068    field
9069        .arguments()
9070        .map(|args| {
9071            args.into_iter()
9072                .map(|(name, value)| {
9073                    (
9074                        name.to_string(),
9075                        plain_numbers(value.into_json().unwrap_or(serde_json::Value::Null)),
9076                    )
9077                })
9078                .collect()
9079        })
9080        .unwrap_or_default()
9081}
9082
9083/// What an embed's arguments narrow its child rows to: a `DISTINCT ON`
9084/// prefix, a predicate, an ordering, a limit and an offset, in the order the
9085/// SQL takes them.
9086type Narrowing = (String, Option<String>, Option<String>, Option<i64>, i64);
9087
9088/// What an embed's arguments narrow its child rows to.
9089///
9090/// A list and an aggregate of that same list take the same four arguments and
9091/// mean the same thing by them; only what is done with the surviving rows
9092/// differs, so the reading of the arguments is shared.
9093#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
9094fn embed_narrowing(
9095    arguments: &HashMap<String, serde_json::Value>,
9096    plan: &postrust_core::embed::EmbedPlan,
9097    schema_cache: &SchemaCache,
9098    relationships: &HashMap<String, Vec<RelationshipField>>,
9099    target_type: &str,
9100    child_alias: &str,
9101    max_rows: Option<i64>,
9102    param_idx: &mut usize,
9103    values: &mut Vec<serde_json::Value>,
9104    names: &crate::names::NameOverrides,
9105    caller: &crate::role::Caller<'_>,
9106) -> Result<Narrowing, async_graphql::Error> {
9107    // The child's own permission, which is the whole reason an embed is a
9108    // place this has to be applied: `author { articles { ... } }` reads two
9109    // tables, and the rule on the second is not the rule on the first.
9110    let permission =
9111        permission_predicate(caller, names, &plan.foreign_schema, &plan.foreign_table)?;
9112    let requested = arguments.get("where").filter(|v| !v.is_null()).cloned();
9113    let child_where = match and_predicates(requested, permission) {
9114        Some(expression) => {
9115            let child_scope = WhereScope::for_alias(
9116                &plan.foreign_schema,
9117                &plan.foreign_table,
9118                child_alias,
9119                target_type,
9120                schema_cache,
9121                relationships,
9122                names,
9123            )
9124            .for_caller(*caller);
9125            let mut nested_alias = 0usize;
9126            build_condition(
9127                &expression,
9128                &child_scope,
9129                param_idx,
9130                values,
9131                &mut nested_alias,
9132            )?
9133        }
9134        None => None,
9135    };
9136
9137    let child_order = match arguments.get("order_by") {
9138        Some(order) if !order.is_null() => order_terms(
9139            order,
9140            schema_cache,
9141            &plan.foreign_schema,
9142            &plan.foreign_table,
9143            child_alias,
9144            names,
9145        )?,
9146        _ => None,
9147    };
9148
9149    // A limit written on the embed is what the client asked for; the
9150    // configured ceiling still applies as an upper bound, the same way it
9151    // does at the top level -- and so does the child's own permission, which
9152    // bounds rows per parent here rather than per query.
9153    let ceiling = match (
9154        max_rows,
9155        crate::role::read_limit(caller, names, &plan.foreign_schema, &plan.foreign_table),
9156    ) {
9157        (Some(configured), Some(granted)) => Some(configured.min(granted)),
9158        (only, None) | (None, only) => only,
9159    };
9160    let child_limit = match arguments.get("limit").and_then(|v| v.as_i64()) {
9161        Some(requested) => match ceiling {
9162            Some(ceiling) => Some(requested.min(ceiling)),
9163            None => Some(requested),
9164        },
9165        None => ceiling,
9166    };
9167    let child_offset = arguments
9168        .get("offset")
9169        .and_then(|v| v.as_i64())
9170        .unwrap_or(0);
9171
9172    // `author { articles(distinct_on: [title], order_by: {title: asc}) }` --
9173    // one article per title, the same question the root field answers and
9174    // under the same rule about what may be ordered by.
9175    let requested = match arguments.get("distinct_on") {
9176        Some(serde_json::Value::Array(items)) => items
9177            .iter()
9178            .filter_map(|item| item.as_str())
9179            .collect::<Vec<_>>(),
9180        Some(serde_json::Value::String(one)) => vec![one.as_str()],
9181        _ => Vec::new(),
9182    };
9183    let mut distinct_on = Vec::with_capacity(requested.len());
9184    if !requested.is_empty() {
9185        let qi = postrust_core::api_request::QualifiedIdentifier::new(
9186            &plan.foreign_schema,
9187            &plan.foreign_table,
9188        );
9189        let table = schema_cache.get_table(&qi).ok_or_else(|| {
9190            async_graphql::Error::new(format!("unknown table \"{}\"", plan.foreign_table))
9191        })?;
9192        for field in requested {
9193            let column = table_column_for(names, table, field).to_string();
9194            if table.get_column(&column).is_none() {
9195                return Err(async_graphql::Error::new(format!(
9196                    "cannot take distinct on unknown column \"{}\" of \"{}\"",
9197                    field, plan.foreign_table
9198                )));
9199            }
9200            distinct_on.push(format!(
9201                "{}.{}",
9202                postrust_sql::escape_ident(child_alias),
9203                postrust_sql::escape_ident(&column)
9204            ));
9205        }
9206    }
9207    let (distinct, child_order) = distinct_on_clause(&distinct_on, child_order.as_deref())?;
9208
9209    Ok((
9210        distinct,
9211        child_where,
9212        child_order,
9213        child_limit,
9214        child_offset,
9215    ))
9216}
9217
9218#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
9219fn build_embed_expressions<'a>(
9220    caller: &crate::role::Caller<'_>,
9221    schema_cache: &SchemaCache,
9222    relationships: &HashMap<String, Vec<RelationshipField>>,
9223    type_name: &str,
9224    parent_alias: &str,
9225    fields: impl IntoIterator<Item = SelectionField<'a>>,
9226    max_rows: Option<i64>,
9227    alias_counter: &mut usize,
9228    param_idx: &mut usize,
9229    values: &mut Vec<serde_json::Value>,
9230    names: &crate::names::NameOverrides,
9231) -> Result<Vec<(String, String)>, async_graphql::Error> {
9232    let Some(available) = relationships.get(type_name) else {
9233        return Ok(Vec::new());
9234    };
9235
9236    let mut expressions = Vec::new();
9237
9238    for field in fields {
9239        // `<relationship>_aggregate` is the same embed with an aggregate
9240        // select list, so it is resolved here rather than by a resolver of its
9241        // own -- the correlation is what makes it a per-parent answer.
9242        let aggregate_of = field
9243            .name()
9244            .strip_suffix("_aggregate")
9245            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list));
9246
9247        if let Some(rel) = aggregate_of {
9248            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9249                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9250            *alias_counter += 1;
9251            let child_alias = format!("a{}", alias_counter);
9252
9253            // The same narrowing an embedded list takes. `where` here decides
9254            // which rows are counted, not which are returned, so it goes on the
9255            // set the aggregate reads rather than on the answer.
9256            let arguments = embed_arguments(field);
9257            let (child_distinct, child_where, child_order, child_limit, child_offset) =
9258                embed_narrowing(
9259                    &arguments,
9260                    &plan,
9261                    schema_cache,
9262                    relationships,
9263                    &rel.target_type,
9264                    &child_alias,
9265                    max_rows,
9266                    param_idx,
9267                    values,
9268                    names,
9269                    caller,
9270                )?;
9271
9272            let child_table =
9273                schema_cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
9274                    &plan.foreign_schema,
9275                    &plan.foreign_table,
9276                ));
9277            let select = nested_aggregate_select(
9278                caller,
9279                field,
9280                &child_alias,
9281                schema_cache,
9282                relationships,
9283                &rel.target_type,
9284                child_table,
9285                max_rows,
9286                alias_counter,
9287                param_idx,
9288                values,
9289                names,
9290            )?;
9291
9292            let call = computed_arguments(
9293                rel,
9294                &plan,
9295                &postrust_sql::escape_ident(parent_alias),
9296                arguments.get("args"),
9297                param_idx,
9298                values,
9299                schema_cache,
9300            )?;
9301            let expression = plan
9302                .aggregate_expression(
9303                    parent_alias,
9304                    &postrust_sql::escape_ident(parent_alias),
9305                    &child_alias,
9306                    &select,
9307                    child_limit,
9308                    child_offset,
9309                    child_where.as_deref(),
9310                    child_order.as_deref(),
9311                    call.as_deref(),
9312                    &child_distinct,
9313                    // A GraphQL body is JSON throughout -- nothing here
9314                    // renders a row as text -- and Hasura keeps the order the
9315                    // fields were selected in.
9316                    postrust_core::EmbedRendering::Verbatim,
9317                )
9318                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9319            expressions.push((field.name().to_string(), expression));
9320            continue;
9321        }
9322
9323        let Some(rel) = available.iter().find(|r| r.name == field.name()) else {
9324            continue;
9325        };
9326
9327        let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9328            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9329
9330        *alias_counter += 1;
9331        let child_alias = format!("e{}", alias_counter);
9332
9333        let nested = build_embed_expressions(
9334            caller,
9335            schema_cache,
9336            relationships,
9337            &rel.target_type,
9338            &child_alias,
9339            field.selection_set(),
9340            max_rows,
9341            alias_counter,
9342            param_idx,
9343            values,
9344            names,
9345        )?;
9346
9347        // The arguments written on the embed itself.
9348        let arguments = embed_arguments(field);
9349        let (child_distinct, child_where, child_order, child_limit, child_offset) =
9350            embed_narrowing(
9351                &arguments,
9352                &plan,
9353                schema_cache,
9354                relationships,
9355                &rel.target_type,
9356                &child_alias,
9357                max_rows,
9358                param_idx,
9359                values,
9360                names,
9361                caller,
9362            )?;
9363
9364        // Leaf fields are columns; anything that resolved to a relationship is
9365        // an expression instead.
9366        let child_relationships = relationships.get(&rel.target_type);
9367        let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
9368            &plan.foreign_schema,
9369            &plan.foreign_table,
9370        );
9371        let child_table = schema_cache.get_table(&child_qi);
9372        let mut parts: Vec<String> = Vec::new();
9373        for sub in field.selection_set() {
9374            let name = sub.name();
9375            let is_relationship = child_relationships
9376                .map(|rels| {
9377                    rels.iter().any(|r| {
9378                        r.name == name || (r.is_list && format!("{}_aggregate", r.name) == name)
9379                    })
9380                })
9381                .unwrap_or(false);
9382            // `__typename` is answered by the schema, not the row -- and it is
9383            // asked for here constantly: Apollo Client adds it to every
9384            // selection, and a Federation router to every entity reference.
9385            if is_relationship || name == "__typename" {
9386                continue;
9387            }
9388            // A computed column inside an embed is the same call, against the
9389            // child's alias rather than the table.
9390            let column = child_table
9391                .map(|t| table_column_for(names, t, name))
9392                .unwrap_or(name);
9393            let computed = child_table
9394                .filter(|t| t.get_column(column).is_none())
9395                .and_then(|t| {
9396                    let function = names
9397                        .computed_source(&t.schema, &t.name, name)
9398                        .unwrap_or(name);
9399                    t.get_computed_column(function)
9400                });
9401            match computed {
9402                Some(definition) => parts.push(format!(
9403                    "{} AS {}",
9404                    computed_call(
9405                        definition,
9406                        &format!("{}.*", postrust_sql::escape_ident(&child_alias)),
9407                        &postrust_sql::escape_ident(&child_alias),
9408                    ),
9409                    postrust_sql::escape_ident(name)
9410                )),
9411                // A column the child exposes under another name is selected as
9412                // that name, since the row it lands in is the client's.
9413                None if column != name => parts.push(format!(
9414                    "{} AS {}",
9415                    postrust_sql::escape_ident(column),
9416                    postrust_sql::escape_ident(name)
9417                )),
9418                None => parts.push(postrust_sql::escape_ident(name)),
9419            }
9420        }
9421        if parts.is_empty() && nested.is_empty() {
9422            parts.push(
9423                child_table
9424                    .and_then(|table| rename_projection(table, &child_alias, names))
9425                    .unwrap_or_else(|| format!("{}.*", postrust_sql::escape_ident(&child_alias))),
9426            );
9427        }
9428        for (field_name, expression) in nested {
9429            parts.push(format!(
9430                "{} AS {}",
9431                expression,
9432                postrust_sql::escape_ident(&field_name)
9433            ));
9434        }
9435
9436        let computed_call_arguments = computed_arguments(
9437            rel,
9438            &plan,
9439            &postrust_sql::escape_ident(parent_alias),
9440            arguments.get("args"),
9441            param_idx,
9442            values,
9443            schema_cache,
9444        )?;
9445        let expression = plan
9446            .embed_expression(
9447                parent_alias,
9448                // A computed relationship is correlated by argument rather
9449                // than by a key: the function takes the parent row, and an
9450                // alias names that row.
9451                &postrust_sql::escape_ident(parent_alias),
9452                &child_alias,
9453                &format!("{}{}", child_distinct, parts.join(", ")),
9454                child_limit,
9455                child_offset,
9456                child_where.as_deref(),
9457                child_order.as_deref(),
9458                computed_call_arguments.as_deref(),
9459                postrust_core::EmbedRendering::Verbatim,
9460            )
9461            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9462
9463        expressions.push((rel.name.clone(), expression));
9464    }
9465
9466    Ok(expressions)
9467}
9468
9469/// What embedding a relationship needs, independent of the rows involved.
9470///
9471/// The connection is passed alongside rather than held here: embedding runs on
9472/// the request's transaction, and a shared reference to this struct could not
9473/// hand out the mutable borrow the queries need.
9474struct EmbedContext<'c> {
9475    schema_cache: &'c SchemaCache,
9476    relationships: &'c HashMap<String, Vec<RelationshipField>>,
9477    max_rows: Option<i64>,
9478}
9479
9480/// Embed the relationship fields requested on `rows`.
9481///
9482/// One query per relationship per level, not one per row: the parents' join
9483/// keys are collected and passed as a single array. Recurses so a nested
9484/// selection costs one further query per relationship at each depth.
9485fn embed_relationships<'f>(
9486    conn: &'f mut sqlx::PgConnection,
9487    ctx: &'f EmbedContext<'f>,
9488    type_name: &'f str,
9489    selection: async_graphql::SelectionField<'f>,
9490    rows: &'f mut [serde_json::Value],
9491) -> futures::future::BoxFuture<'f, Result<(), async_graphql::Error>> {
9492    Box::pin(async move {
9493        if rows.is_empty() {
9494            return Ok(());
9495        }
9496
9497        let Some(available) = ctx.relationships.get(type_name) else {
9498            return Ok(());
9499        };
9500
9501        for requested in selection.selection_set() {
9502            let Some(rel) = available.iter().find(|r| r.name == requested.name()) else {
9503                continue;
9504            };
9505
9506            let plan =
9507                postrust_core::embed::EmbedPlan::resolve(&rel.relationship, ctx.schema_cache)
9508                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9509
9510            let keys = postrust_core::embed::parent_keys(rows, &plan.local_column);
9511
9512            // Project only what the selection asked for. A GraphQL selection
9513            // names its leaf fields, so the columns are known before the query
9514            // and an unrequested column need not be read, serialised, sent and
9515            // parsed just to be dropped.
9516            //
9517            // A nested relationship joins on a column of the child row, so that
9518            // column is added even when it was not selected; it is removed again
9519            // when the response is shaped. A selection whose sub-fields cannot
9520            // all be resolved to columns falls back to every column.
9521            let mut child_columns: Vec<String> = Vec::new();
9522            let mut project_everything = false;
9523            let child_relationships = ctx.relationships.get(&rel.target_type);
9524            for field in requested.selection_set() {
9525                let name = field.name();
9526                match child_relationships.and_then(|rels| rels.iter().find(|r| r.name == name)) {
9527                    Some(nested_rel) => {
9528                        match postrust_core::embed::EmbedPlan::resolve(
9529                            &nested_rel.relationship,
9530                            ctx.schema_cache,
9531                        ) {
9532                            Ok(nested_plan) => child_columns.push(nested_plan.local_column),
9533                            Err(_) => project_everything = true,
9534                        }
9535                    }
9536                    // Answered by the schema, not a column.
9537                    None if name == "__typename" => {}
9538                    None => child_columns.push(name.to_string()),
9539                }
9540            }
9541            if project_everything || child_columns.is_empty() {
9542                child_columns.clear();
9543            }
9544
9545            let mut grouped = if keys.is_empty() {
9546                std::collections::HashMap::new()
9547            } else {
9548                let sql = plan
9549                    .children_grouped_sql(ctx.max_rows, &child_columns)
9550                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9551
9552                let fetched = sqlx::query(&sql)
9553                    .bind(&keys)
9554                    .fetch_all(&mut *conn)
9555                    .await
9556                    .map_err(database_error)?;
9557
9558                // The query returns the join key and a JSON array of that key's
9559                // children, grouped by PostgreSQL rather than row by row here.
9560                use sqlx::Row;
9561                let pairs: Vec<(serde_json::Value, serde_json::Value)> = fetched
9562                    .into_iter()
9563                    .filter_map(|row| {
9564                        Some((
9565                            row.try_get::<serde_json::Value, _>(0).ok()?,
9566                            row.try_get::<serde_json::Value, _>(1).ok()?,
9567                        ))
9568                    })
9569                    .collect();
9570
9571                postrust_core::embed::group_from_aggregated(pairs)
9572            };
9573
9574            // Recurse before attaching, so nested embeds land in the values
9575            // copied onto the parents. One query serves every child row at this
9576            // level, so the rows are flattened for the call and put back
9577            // afterwards; skipped when the selection asks for nothing deeper.
9578            let has_deeper_embed = ctx
9579                .relationships
9580                .get(&rel.target_type)
9581                .map(|rels| {
9582                    requested
9583                        .selection_set()
9584                        .any(|field| rels.iter().any(|r| r.name == field.name()))
9585                })
9586                .unwrap_or(false);
9587
9588            if has_deeper_embed {
9589                let mut order: Vec<(String, usize)> = Vec::with_capacity(grouped.len());
9590                let mut flat: Vec<serde_json::Value> = Vec::new();
9591                for (key, children) in grouped.drain() {
9592                    order.push((key, children.len()));
9593                    flat.extend(children);
9594                }
9595
9596                embed_relationships(&mut *conn, ctx, &rel.target_type, requested, &mut flat)
9597                    .await?;
9598
9599                let mut rest = flat.into_iter();
9600                for (key, count) in order {
9601                    grouped.insert(key, rest.by_ref().take(count).collect());
9602                }
9603            }
9604
9605            for row in rows.iter_mut() {
9606                postrust_core::embed::attach_to_parent(row, &rel.name, &plan, &grouped);
9607            }
9608        }
9609
9610        Ok(())
9611    })
9612}
9613
9614/// Build a `where` document that addresses exactly one row by primary key.
9615///
9616/// Used by the by-PK mutations, which take the key columns as arguments instead
9617/// of a free-form `where`.
9618fn pk_where_from_args(
9619    ctx: &ResolverContext<'_>,
9620    schema_name: &str,
9621    table_name: &str,
9622    pk_columns: &[(String, String)],
9623    names: &crate::names::NameOverrides,
9624) -> Result<serde_json::Value, async_graphql::Error> {
9625    if pk_columns.is_empty() {
9626        return Err(async_graphql::Error::new(format!(
9627            "\"{}\" has no primary key, so it cannot be mutated by key",
9628            table_name
9629        )));
9630    }
9631
9632    // `update_x_by_pk` takes its key as one `pk_columns` object; the others
9633    // take the key columns as arguments of their own. Both spellings are what
9634    // a client was generated against, so both are read here.
9635    let from_object = ctx
9636        .args
9637        .try_get("pk_columns")
9638        .ok()
9639        .map(|v| accessor_to_json(&v));
9640
9641    let mut conditions = serde_json::Map::new();
9642    for (col_name, _) in pk_columns {
9643        // Under the name the key column is exposed as, on both sides: the
9644        // argument the client wrote, and the predicate handed on to the same
9645        // builder every other `where` goes through.
9646        let field = names
9647            .column(schema_name, table_name, col_name)
9648            .unwrap_or(col_name);
9649        let value = match &from_object {
9650            Some(serde_json::Value::Object(map)) => map.get(field).cloned().ok_or_else(|| {
9651                async_graphql::Error::new(format!(
9652                    "pk_columns is missing the key column \"{}\"",
9653                    field
9654                ))
9655            })?,
9656            _ => {
9657                let arg = ctx.args.try_get(field).map_err(|_| {
9658                    async_graphql::Error::new(format!(
9659                        "missing required primary key argument \"{}\"",
9660                        field
9661                    ))
9662                })?;
9663                accessor_to_json(&arg)
9664            }
9665        };
9666        conditions.insert(field.to_string(), serde_json::json!({ "_eq": value }));
9667    }
9668
9669    Ok(serde_json::Value::Object(conditions))
9670}
9671
9672/// GraphQL scalar name to use for a primary key argument of the given
9673/// PostgreSQL type.
9674///
9675/// Falls back to `String` for anything that does not map to a plain scalar --
9676/// a composite or array key cannot be expressed as a single named argument, and
9677/// the value is cast to the column's type in SQL anyway.
9678fn pk_argument_type(pg_type: &str) -> String {
9679    let rendered = crate::types::pg_type_to_graphql(pg_type).to_string();
9680    if rendered.starts_with('[') {
9681        "String".to_string()
9682    } else {
9683        rendered
9684    }
9685}
9686
9687/// The public GraphQL type of a primary-key argument.
9688///
9689/// SQL execution still carries the PostgreSQL type beside the key and uses it
9690/// for casts and binding. This is only the schema-facing type.
9691fn exposed_pk_argument_type(
9692    names: &crate::names::NameOverrides,
9693    schema: &str,
9694    table: &str,
9695    column: &str,
9696    pg_type: &str,
9697) -> String {
9698    names
9699        .column_type(schema, table, column)
9700        .map(|given| given.to_string())
9701        .unwrap_or_else(|| pk_argument_type(pg_type))
9702}
9703
9704/// Convert a GraphQL type string to a TypeRef.
9705fn graphql_type_ref(type_str: &str) -> TypeRef {
9706    // Parse type string like "[Users!]!" or "String" or "Int!"
9707    let is_list = type_str.starts_with('[');
9708    let is_nn = type_str.ends_with('!');
9709
9710    // Strip outer modifiers: first the trailing !, then the brackets
9711    let inner = if is_list {
9712        let stripped = type_str
9713            .trim_end_matches('!') // Remove outer !
9714            .trim_start_matches('[') // Remove [
9715            .trim_end_matches(']'); // Remove ]
9716        stripped
9717    } else {
9718        type_str.trim_end_matches('!')
9719    };
9720
9721    let inner_nn = inner.ends_with('!');
9722    let base_type = inner.trim_end_matches('!');
9723
9724    if is_list {
9725        if is_nn {
9726            if inner_nn {
9727                TypeRef::named_nn_list_nn(base_type)
9728            } else {
9729                TypeRef::named_list_nn(base_type)
9730            }
9731        } else if inner_nn {
9732            TypeRef::named_nn_list(base_type)
9733        } else {
9734            TypeRef::named_list(base_type)
9735        }
9736    } else if is_nn {
9737        TypeRef::named_nn(base_type)
9738    } else {
9739        TypeRef::named(base_type)
9740    }
9741}
9742
9743/// The key serde_json wraps a number in when it is keeping its digits.
9744///
9745/// This crate reads `serde_json` with `arbitrary_precision`, so a PostgreSQL
9746/// `numeric` survives a round trip through JSON with every digit it had. The
9747/// cost is on the way in: a fractional number in a *variable* is deserialized
9748/// into async-graphql's own value type as a one-key object holding the text,
9749/// because the generic path cannot know it is looking at a number. Integers
9750/// are unaffected, which is why this went unnoticed until a shape arrived by
9751/// variable and every coordinate in it read as zero.
9752const NUMBER_AS_TEXT: &str = "$serde_json::private::Number";
9753
9754/// The number an arbitrary-precision wrapper is holding, if that is what this
9755/// object is.
9756fn number_kept_as_text(
9757    map: &serde_json::Map<String, serde_json::Value>,
9758) -> Option<serde_json::Value> {
9759    if map.len() != 1 {
9760        return None;
9761    }
9762    let text = map.get(NUMBER_AS_TEXT)?.as_str()?;
9763    serde_json::from_str::<serde_json::Value>(text)
9764        .ok()
9765        .filter(|value| value.is_number())
9766}
9767
9768/// The same, applied to a whole document that came in as one.
9769fn plain_numbers(value: serde_json::Value) -> serde_json::Value {
9770    match value {
9771        serde_json::Value::Object(map) => match number_kept_as_text(&map) {
9772            Some(number) => number,
9773            None => serde_json::Value::Object(
9774                map.into_iter()
9775                    .map(|(k, v)| (k, plain_numbers(v)))
9776                    .collect(),
9777            ),
9778        },
9779        serde_json::Value::Array(items) => {
9780            serde_json::Value::Array(items.into_iter().map(plain_numbers).collect())
9781        }
9782        other => other,
9783    }
9784}
9785
9786/// Convert ValueAccessor to JSON.
9787fn accessor_to_json(accessor: &ValueAccessor<'_>) -> serde_json::Value {
9788    // Use the deserialize method if available, or convert manually
9789    if accessor.is_null() {
9790        serde_json::Value::Null
9791    } else if let Ok(b) = accessor.boolean() {
9792        serde_json::Value::Bool(b)
9793    } else if let Ok(i) = accessor.i64() {
9794        serde_json::Value::Number(i.into())
9795    } else if let Ok(f) = accessor.f64() {
9796        serde_json::Number::from_f64(f)
9797            .map(serde_json::Value::Number)
9798            .unwrap_or(serde_json::Value::Null)
9799    } else if let Ok(s) = accessor.string() {
9800        serde_json::Value::String(s.to_string())
9801    } else if let Ok(name) = accessor.enum_name() {
9802        // An enum value is not a string to `string()`, and falling through to
9803        // null made every `order_by: {name: asc}` read as an empty direction.
9804        serde_json::Value::String(name.to_string())
9805    } else if let Ok(list) = accessor.list() {
9806        serde_json::Value::Array(list.iter().map(|v| accessor_to_json(&v)).collect())
9807    } else if let Ok(obj) = accessor.object() {
9808        let map: serde_json::Map<String, serde_json::Value> = obj
9809            .iter()
9810            .map(|(k, v)| (k.to_string(), accessor_to_json(&v)))
9811            .collect();
9812        match number_kept_as_text(&map) {
9813            Some(number) => number,
9814            None => serde_json::Value::Object(map),
9815        }
9816    } else {
9817        serde_json::Value::Null
9818    }
9819}
9820
9821/// Convert async-graphql Value to JSON.
9822fn value_to_json(value: &Value) -> serde_json::Value {
9823    match value {
9824        Value::Null => serde_json::Value::Null,
9825        Value::Boolean(b) => serde_json::Value::Bool(*b),
9826        Value::Number(n) => {
9827            if let Some(i) = n.as_i64() {
9828                serde_json::Value::Number(i.into())
9829            } else if let Some(f) = n.as_f64() {
9830                serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
9831            } else {
9832                serde_json::Value::Null
9833            }
9834        }
9835        Value::String(s) => serde_json::Value::String(s.clone()),
9836        Value::List(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
9837        Value::Object(obj) => {
9838            let map: serde_json::Map<String, serde_json::Value> = obj
9839                .iter()
9840                .map(|(k, v)| (k.to_string(), value_to_json(v)))
9841                .collect();
9842            serde_json::Value::Object(map)
9843        }
9844        Value::Binary(b) => serde_json::Value::String(base64::Engine::encode(
9845            &base64::engine::general_purpose::STANDARD,
9846            b,
9847        )),
9848        Value::Enum(e) => serde_json::Value::String(e.to_string()),
9849    }
9850}
9851
9852/// Convert JSON to async-graphql Value.
9853fn json_to_value(json: serde_json::Value) -> Value {
9854    match json {
9855        serde_json::Value::Null => Value::Null,
9856        serde_json::Value::Bool(b) => Value::Boolean(b),
9857        serde_json::Value::Number(n) => {
9858            if let Some(i) = n.as_i64() {
9859                Value::Number(i.into())
9860            } else if let Some(f) = n.as_f64() {
9861                Value::Number(async_graphql::Number::from_f64(f).unwrap())
9862            } else {
9863                Value::Null
9864            }
9865        }
9866        serde_json::Value::String(s) => Value::String(s),
9867        serde_json::Value::Array(arr) => Value::List(arr.into_iter().map(json_to_value).collect()),
9868        serde_json::Value::Object(obj) => {
9869            let map: indexmap::IndexMap<async_graphql::Name, Value> = obj
9870                .into_iter()
9871                .map(|(k, v)| (async_graphql::Name::new(k), json_to_value(v)))
9872                .collect();
9873            Value::Object(map)
9874        }
9875    }
9876}
9877
9878/// Register filter input types.
9879///
9880/// These are currently unreachable: the `filter` and `where` arguments are
9881/// declared as the `JSON` scalar, so no field references these input objects
9882/// and async-graphql prunes them from the published schema (introspecting
9883/// `IntFilterInput` returns null). They are kept as the shape to move to if
9884/// filters become typed per column; until then the operators a filter actually
9885/// supports are the ones `build_where_clause` implements, and it rejects
9886/// anything else rather than ignoring it.
9887#[cfg(test)]
9888mod tests {
9889    use super::*;
9890    use indexmap::IndexMap;
9891    use postrust_core::schema_cache::{Cardinality, Column, Relationship, Table};
9892    use postrust_core::QualifiedIdentifier;
9893    use std::collections::{HashMap, HashSet};
9894
9895    fn entity_key_column(pg_type: &str, explicit_id: bool) -> EntityKeyColumn {
9896        EntityKeyColumn {
9897            field_name: "id".to_string(),
9898            column_name: "id".to_string(),
9899            pg_type: pg_type.to_string(),
9900            explicit_id,
9901        }
9902    }
9903
9904    #[test]
9905    fn federation_integer_keys_reject_strings() {
9906        let column = entity_key_column("int4", false);
9907
9908        assert_eq!(
9909            normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap(),
9910            serde_json::json!(2)
9911        );
9912        let error = normalize_entity_key_value("users", &column, &serde_json::json!("2"))
9913            .expect_err("a GraphQL Int cannot be a string");
9914        assert_eq!(
9915            error.message,
9916            "entity representation for \"users\" has invalid key field \"id\": expected a value compatible with PostgreSQL type \"int4\", found a string"
9917        );
9918    }
9919
9920    #[test]
9921    fn federation_bigint_keys_accept_exact_decimal_strings() {
9922        let column = entity_key_column("int8", false);
9923
9924        assert_eq!(
9925            normalize_entity_key_value("users", &column, &serde_json::json!(7)).unwrap(),
9926            serde_json::json!(7)
9927        );
9928        assert_eq!(
9929            normalize_entity_key_value("users", &column, &serde_json::json!("9007199254740993"))
9930                .unwrap(),
9931            serde_json::json!(9_007_199_254_740_993_i64)
9932        );
9933        assert!(normalize_entity_key_value(
9934            "users",
9935            &column,
9936            &serde_json::json!("9223372036854775808")
9937        )
9938        .is_err());
9939        assert!(normalize_entity_key_value("users", &column, &serde_json::json!("7.5")).is_err());
9940    }
9941
9942    #[test]
9943    fn federation_numeric_keys_accept_exact_decimal_strings() {
9944        let column = entity_key_column("numeric", false);
9945
9946        assert_eq!(
9947            normalize_entity_key_value("users", &column, &serde_json::json!(7.5)).unwrap(),
9948            serde_json::json!("7.5")
9949        );
9950        assert_eq!(
9951            normalize_entity_key_value(
9952                "users",
9953                &column,
9954                &serde_json::json!("9007199254740993.123456789")
9955            )
9956            .unwrap(),
9957            serde_json::json!("9007199254740993.123456789")
9958        );
9959        assert!(
9960            normalize_entity_key_value("users", &column, &serde_json::json!("not-a-number"))
9961                .is_err()
9962        );
9963    }
9964
9965    #[test]
9966    fn federation_integer_backed_ids_have_one_normal_form() {
9967        let column = entity_key_column("int4", true);
9968
9969        let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
9970        let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
9971        assert_eq!(number, serde_json::json!(2));
9972        assert_eq!(string, number);
9973        assert!(normalize_entity_key_value("users", &column, &serde_json::json!("two")).is_err());
9974    }
9975
9976    #[test]
9977    fn federation_text_backed_ids_normalize_integer_input_to_text() {
9978        let column = entity_key_column("text", true);
9979
9980        let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
9981        let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
9982        assert_eq!(number, serde_json::json!("2"));
9983        assert_eq!(string, number);
9984    }
9985
9986    #[test]
9987    fn federation_uuid_backed_ids_are_validated_and_canonicalized() {
9988        let column = entity_key_column("uuid", true);
9989
9990        assert_eq!(
9991            normalize_entity_key_value(
9992                "users",
9993                &column,
9994                &serde_json::json!("550E8400-E29B-41D4-A716-446655440000")
9995            )
9996            .unwrap(),
9997            serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
9998        );
9999        assert!(
10000            normalize_entity_key_value("users", &column, &serde_json::json!("not-a-uuid")).is_err()
10001        );
10002    }
10003
10004    fn create_test_table(name: &str) -> Table {
10005        let mut columns = IndexMap::new();
10006        columns.insert(
10007            "id".into(),
10008            Column {
10009                name: "id".into(),
10010                description: None,
10011                nullable: false,
10012                data_type: "integer".into(),
10013                nominal_type: "int4".into(),
10014                max_len: None,
10015                default: Some("nextval('id_seq')".into()),
10016                enum_values: vec![],
10017                is_pk: true,
10018                position: 1,
10019                always_generated: false,
10020                domain_type: None,
10021            },
10022        );
10023        columns.insert(
10024            "name".into(),
10025            Column {
10026                name: "name".into(),
10027                description: None,
10028                nullable: false,
10029                data_type: "text".into(),
10030                nominal_type: "text".into(),
10031                max_len: None,
10032                default: None,
10033                enum_values: vec![],
10034                is_pk: false,
10035                position: 2,
10036                always_generated: false,
10037                domain_type: None,
10038            },
10039        );
10040
10041        Table {
10042            schema: "public".into(),
10043            name: name.into(),
10044            description: None,
10045            is_view: false,
10046            insertable: true,
10047            updatable: true,
10048            deletable: true,
10049            pk_cols: vec!["id".into()],
10050            unique_constraints: Vec::new(),
10051            columns,
10052            computed_columns: Default::default(),
10053            is_partitioned: false,
10054        }
10055    }
10056
10057    fn create_test_schema_cache() -> SchemaCache {
10058        let mut tables = HashMap::new();
10059        let users = create_test_table("users");
10060        tables.insert(users.qualified_identifier(), users);
10061
10062        SchemaCache {
10063            tables,
10064            relationships: HashMap::new(),
10065            routines: HashMap::new(),
10066            timezones: HashSet::new(),
10067            media_handlers: HashMap::new(),
10068            pg_version: 150000,
10069            representations: Default::default(),
10070        }
10071    }
10072
10073    fn add_test_table(cache: &mut SchemaCache, name: &str) {
10074        let table = create_test_table(name);
10075        cache.tables.insert(table.qualified_identifier(), table);
10076    }
10077
10078    fn add_test_posts_relationship(cache: &mut SchemaCache) {
10079        let users = QualifiedIdentifier::new("public", "users");
10080        cache.relationships.insert(
10081            (users.clone(), "public".into()),
10082            vec![Relationship::ForeignKey {
10083                table: users,
10084                foreign_table: QualifiedIdentifier::new("public", "posts"),
10085                is_self: false,
10086                cardinality: Cardinality::O2M {
10087                    constraint: "posts_user_id_fkey".into(),
10088                    columns: vec![("id".into(), "user_id".into())],
10089                },
10090                table_is_view: false,
10091                foreign_table_is_view: false,
10092                constraint_name: "posts_user_id_fkey".into(),
10093            }],
10094        );
10095    }
10096
10097    // ============================================================================
10098    // Type Reference Tests
10099    // ============================================================================
10100
10101    #[test]
10102    fn test_graphql_type_ref_simple() {
10103        let _type_ref = graphql_type_ref("String");
10104        // TypeRef doesn't implement PartialEq, so we just test it doesn't panic
10105    }
10106
10107    #[test]
10108    fn test_graphql_type_ref_non_null() {
10109        let _type_ref = graphql_type_ref("String!");
10110    }
10111
10112    #[test]
10113    fn test_graphql_type_ref_list() {
10114        let _type_ref = graphql_type_ref("[String]");
10115    }
10116
10117    #[test]
10118    fn test_graphql_type_ref_list_non_null() {
10119        let _type_ref = graphql_type_ref("[String!]!");
10120    }
10121
10122    // ============================================================================
10123    // Value Conversion Tests
10124    // ============================================================================
10125
10126    #[test]
10127    fn test_value_to_json_null() {
10128        let value = Value::Null;
10129        let json = value_to_json(&value);
10130        assert_eq!(json, serde_json::Value::Null);
10131    }
10132
10133    #[test]
10134    fn test_value_to_json_boolean() {
10135        let value = Value::Boolean(true);
10136        let json = value_to_json(&value);
10137        assert_eq!(json, serde_json::Value::Bool(true));
10138    }
10139
10140    #[test]
10141    fn test_value_to_json_number() {
10142        let value = Value::Number(42.into());
10143        let json = value_to_json(&value);
10144        assert_eq!(json, serde_json::json!(42));
10145    }
10146
10147    #[test]
10148    fn test_value_to_json_string() {
10149        let value = Value::String("hello".to_string());
10150        let json = value_to_json(&value);
10151        assert_eq!(json, serde_json::Value::String("hello".to_string()));
10152    }
10153
10154    #[test]
10155    fn test_value_to_json_list() {
10156        let value = Value::List(vec![Value::Number(1.into()), Value::Number(2.into())]);
10157        let json = value_to_json(&value);
10158        assert_eq!(json, serde_json::json!([1, 2]));
10159    }
10160
10161    #[test]
10162    fn test_json_to_value_null() {
10163        let json = serde_json::Value::Null;
10164        let value = json_to_value(json);
10165        assert!(matches!(value, Value::Null));
10166    }
10167
10168    #[test]
10169    fn test_json_to_value_boolean() {
10170        let json = serde_json::Value::Bool(false);
10171        let value = json_to_value(json);
10172        assert!(matches!(value, Value::Boolean(false)));
10173    }
10174
10175    #[test]
10176    fn test_json_to_value_number() {
10177        let json = serde_json::json!(123);
10178        let value = json_to_value(json);
10179        assert!(matches!(value, Value::Number(_)));
10180    }
10181
10182    #[test]
10183    fn test_json_to_value_string() {
10184        let json = serde_json::Value::String("test".to_string());
10185        let value = json_to_value(json);
10186        assert!(matches!(value, Value::String(_)));
10187    }
10188
10189    #[test]
10190    fn test_json_to_value_array() {
10191        let json = serde_json::json!([1, 2, 3]);
10192        let value = json_to_value(json);
10193        assert!(matches!(value, Value::List(_)));
10194    }
10195
10196    #[test]
10197    fn test_json_to_value_object() {
10198        let json = serde_json::json!({"key": "value"});
10199        let value = json_to_value(json);
10200        assert!(matches!(value, Value::Object(_)));
10201    }
10202
10203    // ============================================================================
10204    // Schema Building Tests
10205    // ============================================================================
10206
10207    #[test]
10208    fn test_build_dynamic_schema() {
10209        let cache = create_test_schema_cache();
10210        let config = SchemaConfig::default();
10211        let generated = build_schema(&cache, &config);
10212
10213        let result = build_dynamic_schema(
10214            &generated,
10215            &cache,
10216            None,
10217            None,
10218            Arc::new(Default::default()),
10219            std::time::Duration::from_secs(30),
10220            None,
10221        );
10222        if let Err(ref e) = result {
10223            eprintln!("Schema build error: {:?}", e);
10224        }
10225        assert!(result.is_ok(), "Schema build failed: {:?}", result.err());
10226    }
10227
10228    #[test]
10229    fn a_column_type_override_reaches_fields_and_key_arguments() {
10230        let cache = create_test_schema_cache();
10231        let names = crate::names::NameOverrides::parse(
10232            r#"{"tables": {"public.users": {"column_types": {"id": "ID"}}}}"#,
10233        )
10234        .unwrap();
10235        let config = SchemaConfig {
10236            names: names.clone(),
10237            ..SchemaConfig::default()
10238        };
10239        let generated = build_schema(&cache, &config);
10240        let schema = build_dynamic_schema(
10241            &generated,
10242            &cache,
10243            None,
10244            None,
10245            Arc::new(names),
10246            std::time::Duration::from_secs(30),
10247            None,
10248        )
10249        .expect("schema with an ID override should build");
10250        let sdl = schema.sdl();
10251
10252        let input_body = |name: &str| {
10253            sdl.split_once(&format!("input {name} {{"))
10254                .and_then(|(_, rest)| rest.split_once('}'))
10255                .map(|(body, _)| body.to_string())
10256                .unwrap_or_else(|| panic!("missing input {name}:\n{sdl}"))
10257        };
10258
10259        assert!(sdl.contains("id: ID!"), "object and input fields:\n{sdl}");
10260        assert!(
10261            sdl.contains("users_by_pk(id: ID!): users"),
10262            "query key argument:\n{sdl}"
10263        );
10264        assert!(
10265            sdl.contains("delete_users_by_pk(id: ID!): users"),
10266            "delete key argument:\n{sdl}"
10267        );
10268        assert!(
10269            input_body("users_pk_columns_input").contains("id: ID!"),
10270            "update key input:\n{sdl}"
10271        );
10272        assert!(
10273            input_body("users_bool_exp").contains("id: ID_comparison_exp"),
10274            "comparison input:\n{sdl}"
10275        );
10276        assert!(
10277            input_body("users_insert_input").contains("id: ID"),
10278            "write input:\n{sdl}"
10279        );
10280    }
10281
10282    #[test]
10283    fn a_type_prefix_names_the_complete_dynamic_schema() {
10284        let cache = create_test_schema_cache();
10285        let config = SchemaConfig {
10286            type_prefix: Some("test".into()),
10287            ..SchemaConfig::default()
10288        };
10289        let generated = build_schema(&cache, &config);
10290        let schema = build_dynamic_schema(
10291            &generated,
10292            &cache,
10293            None,
10294            None,
10295            Arc::new(Default::default()),
10296            std::time::Duration::from_secs(30),
10297            None,
10298        )
10299        .expect("a prefixed schema should build");
10300        let sdl = schema.sdl();
10301
10302        assert!(sdl.contains("type test_users "), "object type:\n{sdl}");
10303        assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10304        assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10305        assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10306        assert!(
10307            sdl.contains("test_users_aggregate"),
10308            "aggregate type:\n{sdl}"
10309        );
10310        assert!(sdl.contains("test_insert_users("), "mutation root:\n{sdl}");
10311    }
10312
10313    #[test]
10314    fn federation_is_absent_by_default() {
10315        let cache = create_test_schema_cache();
10316        let generated = build_schema(&cache, &SchemaConfig::default());
10317        let schema = build_dynamic_schema(
10318            &generated,
10319            &cache,
10320            None,
10321            None,
10322            Arc::new(Default::default()),
10323            std::time::Duration::from_secs(30),
10324            None,
10325        )
10326        .expect("a non-federated schema should build");
10327        let sdl = schema.sdl();
10328
10329        assert!(!sdl.contains("_Service"), "service type:\n{sdl}");
10330        assert!(!sdl.contains("_service"), "service field:\n{sdl}");
10331        assert!(!sdl.contains("@key"), "federation key:\n{sdl}");
10332    }
10333
10334    #[test]
10335    fn federation_entity_selections_respect_fragment_type_conditions() {
10336        let document = async_graphql::parser::parse_query(
10337            r#"
10338            query {
10339              _entities(representations: []) {
10340                __typename
10341                ... on authors { id items { title } }
10342                ...ShopFields
10343                ... { common }
10344                ...EntityName
10345              }
10346            }
10347            fragment ShopFields on shops { id items { sku } }
10348            fragment EntityName on _Entity { __typename }
10349            "#,
10350        )
10351        .expect("query parses");
10352        let (_, operation) = document.operations.iter().next().expect("one operation");
10353        let entity = operation
10354            .node
10355            .selection_set
10356            .node
10357            .items
10358            .iter()
10359            .find_map(|selection| match &selection.node {
10360                Selection::Field(field) if field.node.name.node == "_entities" => Some(&field.node),
10361                _ => None,
10362            })
10363            .expect("_entities field");
10364
10365        assert_eq!(
10366            entity_field_applicability(&entity.selection_set.node, &document.fragments, "authors"),
10367            vec![true, true, true, false, false, true, true]
10368        );
10369        assert_eq!(
10370            entity_field_applicability(&entity.selection_set.node, &document.fragments, "shops"),
10371            vec![true, false, false, true, true, true, true]
10372        );
10373    }
10374
10375    #[tokio::test]
10376    async fn federation_entity_lists_allow_null_members() {
10377        let entity =
10378            Object::new("widgets").field(Field::new("id", TypeRef::named_nn(TypeRef::INT), |_| {
10379                FieldFuture::new(async { Ok(Some(Value::from(2))) })
10380            }));
10381        let union = Union::new("_Entity").possible_type("widgets");
10382        let query = Object::new("Query").field(Field::new(
10383            "_entities",
10384            TypeRef::named_list_nn("_Entity"),
10385            |_| {
10386                FieldFuture::new(async {
10387                    Ok(Some(FieldValue::list([
10388                        FieldValue::NULL,
10389                        FieldValue::NULL.with_type("widgets"),
10390                    ])))
10391                })
10392            },
10393        ));
10394        let schema = Schema::build("Query", None, None)
10395            .extension(NullableFederationEntity)
10396            .register(entity)
10397            .register(union)
10398            .register(query)
10399            .finish()
10400            .expect("schema builds");
10401
10402        let response = schema
10403            .execute("{ _entities { ... on widgets { id } } }")
10404            .await;
10405
10406        assert!(response.errors.is_empty(), "{:?}", response.errors);
10407        assert_eq!(
10408            response.data,
10409            async_graphql::value!({ "_entities": [null, { "id": 2 }] })
10410        );
10411    }
10412
10413    #[test]
10414    fn federation_metadata_is_byte_for_byte_inert_when_disabled() {
10415        let cache = create_test_schema_cache();
10416        let sdl_for = |config: SchemaConfig| {
10417            let generated = build_schema(&cache, &config);
10418            build_dynamic_schema(
10419                &generated,
10420                &cache,
10421                None,
10422                None,
10423                Arc::new(config.names.clone()),
10424                std::time::Duration::from_secs(30),
10425                None,
10426            )
10427            .expect("schema should build")
10428            .sdl()
10429        };
10430
10431        let baseline = sdl_for(SchemaConfig {
10432            type_prefix: Some("test".into()),
10433            ..SchemaConfig::default()
10434        });
10435        let with_disabled_federation_metadata = sdl_for(SchemaConfig {
10436            enable_federation: false,
10437            type_prefix: Some("test".into()),
10438            names: crate::names::NameOverrides::parse(
10439                r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10440            )
10441            .expect("metadata"),
10442            ..SchemaConfig::default()
10443        });
10444
10445        assert_eq!(with_disabled_federation_metadata, baseline);
10446        assert!(
10447            with_disabled_federation_metadata.contains("type test_users "),
10448            "prefixed object type remains when federation is disabled:\n{with_disabled_federation_metadata}"
10449        );
10450        assert!(
10451            !with_disabled_federation_metadata.contains("_Service"),
10452            "service type:\n{with_disabled_federation_metadata}"
10453        );
10454        assert!(
10455            !with_disabled_federation_metadata.contains("@key"),
10456            "federation key:\n{with_disabled_federation_metadata}"
10457        );
10458    }
10459
10460    #[test]
10461    fn shared_entities_make_relationship_fields_shareable() {
10462        let federation_sdl = |shared: bool| {
10463            let mut cache = create_test_schema_cache();
10464            add_test_table(&mut cache, "posts");
10465            add_test_posts_relationship(&mut cache);
10466            let names = match shared {
10467                true => crate::names::NameOverrides::parse(
10468                    r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10469                )
10470                .expect("metadata"),
10471                false => crate::names::NameOverrides::default(),
10472            };
10473            let config = SchemaConfig {
10474                enable_federation: true,
10475                type_prefix: Some("test".into()),
10476                names: names.clone(),
10477                ..SchemaConfig::default()
10478            };
10479            let generated = build_schema(&cache, &config);
10480            build_dynamic_schema(
10481                &generated,
10482                &cache,
10483                None,
10484                None,
10485                Arc::new(names),
10486                std::time::Duration::from_secs(30),
10487                None,
10488            )
10489            .expect("federated schema should build")
10490            .sdl_with_options(async_graphql::SDLExportOptions::new().federation())
10491        };
10492
10493        let shared = federation_sdl(true);
10494        assert!(
10495            shared.contains("): [test_posts!]! @shareable"),
10496            "shared relationship field:\n{shared}"
10497        );
10498        assert!(
10499            shared.contains("): test_posts_aggregate! @shareable"),
10500            "shared relationship aggregate field:\n{shared}"
10501        );
10502
10503        let not_shared = federation_sdl(false);
10504        assert!(
10505            not_shared.contains("): [test_posts!]!"),
10506            "non-shared relationship field:\n{not_shared}"
10507        );
10508        assert!(
10509            !not_shared.contains("): [test_posts!]! @shareable"),
10510            "non-shared relationship field must not be shareable:\n{not_shared}"
10511        );
10512        assert!(
10513            not_shared.contains("): test_posts_aggregate!"),
10514            "non-shared relationship aggregate field:\n{not_shared}"
10515        );
10516        assert!(
10517            !not_shared.contains("): test_posts_aggregate! @shareable"),
10518            "non-shared relationship aggregate must not be shareable:\n{not_shared}"
10519        );
10520    }
10521
10522    #[tokio::test]
10523    async fn federation_exposes_service_sdl_and_entity_keys() {
10524        let mut cache = create_test_schema_cache();
10525        add_test_table(&mut cache, "posts");
10526        let config = SchemaConfig {
10527            enable_federation: true,
10528            type_prefix: Some("test".into()),
10529            names: crate::names::NameOverrides::parse(
10530                r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10531            )
10532            .expect("metadata"),
10533            ..SchemaConfig::default()
10534        };
10535        let generated = build_schema(&cache, &config);
10536        let schema = build_dynamic_schema(
10537            &generated,
10538            &cache,
10539            None,
10540            None,
10541            Arc::new(Default::default()),
10542            std::time::Duration::from_secs(30),
10543            None,
10544        )
10545        .expect("a federated schema should build");
10546        let sdl = schema.sdl();
10547        let federation_sdl =
10548            schema.sdl_with_options(async_graphql::SDLExportOptions::new().federation());
10549
10550        assert!(sdl.contains("_Service"), "service type:\n{sdl}");
10551        assert!(sdl.contains("_service"), "service field:\n{sdl}");
10552        assert!(
10553            federation_sdl.contains("type users @key(fields: \"id\")"),
10554            "users key:\n{federation_sdl}"
10555        );
10556        assert!(
10557            federation_sdl.contains("@shareable"),
10558            "shared field directive:\n{federation_sdl}"
10559        );
10560        assert!(sdl.contains("type users "), "shared object type:\n{sdl}");
10561        assert!(
10562            !sdl.contains("type test_users "),
10563            "prefixed shared object type:\n{sdl}"
10564        );
10565        assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10566        assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10567        assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10568        assert!(
10569            !sdl.contains("input users_bool_exp "),
10570            "unprefixed derived input:\n{sdl}"
10571        );
10572        assert!(
10573            sdl.contains("test_users_aggregate"),
10574            "aggregate type:\n{sdl}"
10575        );
10576        assert!(
10577            !sdl.contains("type users_aggregate "),
10578            "unprefixed aggregate type:\n{sdl}"
10579        );
10580        assert!(
10581            sdl.contains("test_users_mutation_response"),
10582            "mutation response type:\n{sdl}"
10583        );
10584        assert!(
10585            !sdl.contains("type users_mutation_response "),
10586            "unprefixed mutation response type:\n{sdl}"
10587        );
10588        assert!(
10589            sdl.contains("type test_posts {"),
10590            "non-shared object type remains prefixed:\n{sdl}"
10591        );
10592        assert!(
10593            !sdl.contains("type posts {"),
10594            "non-shared object type must not become unprefixed:\n{sdl}"
10595        );
10596        assert!(sdl.contains("test_posts("), "prefixed posts root:\n{sdl}");
10597        assert!(
10598            sdl.contains("test_posts_bool_exp"),
10599            "prefixed posts bool_exp:\n{sdl}"
10600        );
10601
10602        let response = schema.execute("{ _service { sdl } }").await;
10603        assert!(
10604            response.errors.is_empty(),
10605            "service query errors: {:?}",
10606            response.errors
10607        );
10608        let service = response.data.into_json().expect("service response JSON");
10609        let service_sdl = service
10610            .get("_service")
10611            .and_then(|value| value.get("sdl"))
10612            .and_then(serde_json::Value::as_str)
10613            .expect("service SDL string");
10614        assert!(
10615            service_sdl.contains("type Query"),
10616            "federation root type:\n{service_sdl}"
10617        );
10618        assert!(
10619            service_sdl.contains("test_users("),
10620            "query fields are on the federation root:\n{service_sdl}"
10621        );
10622        assert!(
10623            service_sdl.contains("test_posts("),
10624            "non-shared query fields are on the federation root:\n{service_sdl}"
10625        );
10626        assert!(
10627            !service_sdl.contains("type query_root"),
10628            "Apollo composition treats query_root as an ordinary object without a schema definition:\n{service_sdl}"
10629        );
10630        assert!(
10631            service_sdl.contains("type users @key(fields: \"id\")"),
10632            "{service_sdl}"
10633        );
10634        assert!(
10635            service_sdl.contains("type test_posts @key(fields: \"id\")"),
10636            "{service_sdl}"
10637        );
10638    }
10639
10640    /// `_exists` becomes a subselect over the table it names, correlated with
10641    /// nothing -- which is the difference between it and a relationship.
10642    #[test]
10643    fn exists_is_a_subselect_over_another_table() {
10644        let cache = create_test_schema_cache();
10645        let names = crate::names::NameOverrides::default();
10646        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10647        let scope = WhereScope::table("public", "users", "users", &names)
10648            .with_resolution(&cache, &relationships);
10649
10650        let mut param_idx = 1usize;
10651        let mut values: Vec<serde_json::Value> = Vec::new();
10652        let mut aliases = 0usize;
10653        let sql = build_condition(
10654            &serde_json::json!({
10655                "_exists": {"_table": "users", "_where": {"name": {"_eq": "Ann"}}}
10656            }),
10657            &scope,
10658            &mut param_idx,
10659            &mut values,
10660            &mut aliases,
10661        )
10662        .expect("compiles")
10663        .expect("constrains something");
10664
10665        assert_eq!(
10666            sql,
10667            "EXISTS (SELECT 1 FROM \"public\".\"users\" AS \"pgrst_exists_1\" \
10668             WHERE \"pgrst_exists_1\".\"name\" = $1::text)"
10669        );
10670        assert_eq!(values, vec![serde_json::json!("Ann")]);
10671    }
10672
10673    /// A `_table` nobody has is refused rather than written into the query.
10674    #[test]
10675    fn exists_names_a_table_the_server_has() {
10676        let cache = create_test_schema_cache();
10677        let names = crate::names::NameOverrides::default();
10678        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10679        let scope = WhereScope::table("public", "users", "users", &names)
10680            .with_resolution(&cache, &relationships);
10681        let error = build_condition(
10682            &serde_json::json!({"_exists": {"_table": "nowhere", "_where": {}}}),
10683            &scope,
10684            &mut 1,
10685            &mut Vec::new(),
10686            &mut 0,
10687        )
10688        .expect_err("a table that is not there is an error");
10689        assert!(
10690            error.message.contains("public.nowhere"),
10691            "{}",
10692            error.message
10693        );
10694    }
10695
10696    /// A role granted "how many" and not "which" gets the count and no rows.
10697    ///
10698    /// A select permission naming no columns, with `allow_aggregations`, is
10699    /// Hasura's way of writing that. The table has an aggregate root and no
10700    /// row type, so `nodes` is not there to be a list of one -- and `count`
10701    /// takes no `columns`, because there are none to name.
10702    #[test]
10703    fn a_table_a_role_may_count_and_not_read_has_a_count_and_no_rows() {
10704        let cache = create_test_schema_cache();
10705        let names = crate::names::NameOverrides::parse(
10706            r#"{"tables": {"public.users": {"permissions":
10707                 {"counter": {"select": {"columns": [], "filter": {},
10708                                         "allow_aggregations": true}}}}}}"#,
10709        )
10710        .unwrap();
10711        let view = crate::role::cache_for_role(&cache, &names, "counter", false);
10712        assert!(!view.tables.is_empty(), "the table is there to be counted");
10713
10714        let config = SchemaConfig {
10715            names: names.clone(),
10716            role: Some("counter".to_string()),
10717            ..SchemaConfig::default()
10718        };
10719        let generated = build_schema(&view, &config);
10720        let schema = build_dynamic_schema(
10721            &generated,
10722            &view,
10723            None,
10724            None,
10725            Arc::new(names),
10726            std::time::Duration::from_secs(30),
10727            Some("counter"),
10728        )
10729        .expect("a role that may only count still has a buildable schema");
10730        let sdl = schema.sdl();
10731
10732        assert!(
10733            sdl.contains("users_aggregate"),
10734            "the count is there:\n{}",
10735            sdl
10736        );
10737        assert!(sdl.contains("count: Int!"), "and it is a count:\n{}", sdl);
10738        // No row type, so nothing that would answer with one.
10739        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10740        assert!(
10741            !sdl.contains("nodes"),
10742            "and no rows beside the numbers:\n{}",
10743            sdl
10744        );
10745        assert!(
10746            !sdl.contains("users_select_column"),
10747            "no column enum to order by or count over:\n{}",
10748            sdl
10749        );
10750        // The functions that read column data are absent with the columns.
10751        assert!(!sdl.contains("users_max_fields"), "no max:\n{}", sdl);
10752    }
10753
10754    /// A role granted `insert` and no `select` gets the write and no type.
10755    ///
10756    /// Hasura's own corpus does this eight times over. What has to hold is
10757    /// that the schema still builds: `insert_users` is there, `users` is not,
10758    /// and the response the insert answers with has no `returning` to name a
10759    /// type that does not exist.
10760    #[test]
10761    fn a_table_a_role_may_only_write_gets_the_write_and_no_type() {
10762        let cache = create_test_schema_cache();
10763        let names = crate::names::NameOverrides::parse(
10764            r#"{"tables": {"public.users": {"permissions":
10765                 {"writer": {"insert": {"columns": "*", "check": {}}}}}}}"#,
10766        )
10767        .unwrap();
10768        let view = crate::role::cache_for_role(&cache, &names, "writer", false);
10769        let config = SchemaConfig {
10770            names: names.clone(),
10771            role: Some("writer".to_string()),
10772            ..SchemaConfig::default()
10773        };
10774        let generated = build_schema(&view, &config);
10775
10776        let schema = build_dynamic_schema(
10777            &generated,
10778            &view,
10779            None,
10780            None,
10781            Arc::new(names),
10782            std::time::Duration::from_secs(30),
10783            Some("writer"),
10784        )
10785        .expect("a role that may only write still has a buildable schema");
10786        let sdl = schema.sdl();
10787
10788        assert!(
10789            sdl.contains("insert_users("),
10790            "the write is there:\n{}",
10791            sdl
10792        );
10793        assert!(
10794            sdl.contains("users_insert_input"),
10795            "and its input:\n{}",
10796            sdl
10797        );
10798        // No row type, and so none of the three fields that answer with one.
10799        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10800        assert!(!sdl.contains("insert_users_one"), "no insert_one:\n{}", sdl);
10801        assert!(
10802            !sdl.contains("returning"),
10803            "and nothing to return:\n{}",
10804            sdl
10805        );
10806        assert!(
10807            sdl.contains("users_mutation_response"),
10808            "the count is still answered:\n{}",
10809            sdl
10810        );
10811        assert!(
10812            !sdl.contains("_mutation_response_mutation_response"),
10813            "and no type is named twice over:\n{}",
10814            sdl
10815        );
10816    }
10817
10818    /// A cache with one VOLATILE function returning rows of `users`, which is
10819    /// the shape `add_to_score` has in Hasura's corpus.
10820    fn cache_with_a_mutation_function() -> SchemaCache {
10821        use postrust_core::schema_cache::{FuncVolatility, RetType, Routine};
10822
10823        let mut cache = create_test_schema_cache();
10824        let routine = Routine {
10825            schema: "public".into(),
10826            name: "add_to_score".into(),
10827            description: None,
10828            params: Vec::new(),
10829            return_type: RetType::SetOf("users".into()),
10830            returns_composite: true,
10831            volatility: FuncVolatility::Volatile,
10832            has_variadic: false,
10833            isolation_level: None,
10834            settings: Vec::new(),
10835            is_procedure: false,
10836            media_type: None,
10837            media_base_type: None,
10838            output_columns: Vec::new(),
10839        };
10840        cache
10841            .routines
10842            .insert(routine.qualified_identifier(), vec![routine]);
10843        cache
10844    }
10845
10846    /// A function with side effects is not granted by permission to read the
10847    /// table it returns. Hasura infers a query function's permission from
10848    /// that select and infers nothing for a mutation, so a role never named
10849    /// by a function permission has no mutation root at all.
10850    #[test]
10851    fn a_mutation_function_needs_the_role_to_be_named() {
10852        let cache = cache_with_a_mutation_function();
10853        let withheld = crate::names::NameOverrides::parse(
10854            r#"{"tables": {"public.users": {"permissions":
10855                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10856                "functions": {"public.add_to_score": {"exposed_as": "mutation"}}}"#,
10857        )
10858        .unwrap();
10859        let granted = crate::names::NameOverrides::parse(
10860            r#"{"tables": {"public.users": {"permissions":
10861                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10862                "functions": {"public.add_to_score":
10863                  {"exposed_as": "mutation", "roles": ["reader"]}}}"#,
10864        )
10865        .unwrap();
10866
10867        let sdl_for = |names: &crate::names::NameOverrides| {
10868            let view = crate::role::cache_for_role(&cache, names, "reader", false);
10869            let config = SchemaConfig {
10870                names: names.clone(),
10871                role: Some("reader".to_string()),
10872                ..SchemaConfig::default()
10873            };
10874            let generated = build_schema(&view, &config);
10875            build_dynamic_schema(
10876                &generated,
10877                &view,
10878                None,
10879                None,
10880                Arc::new(names.clone()),
10881                std::time::Duration::from_secs(30),
10882                Some("reader"),
10883            )
10884            .expect("schema builds")
10885            .sdl()
10886        };
10887
10888        let without = sdl_for(&withheld);
10889        assert!(
10890            !without.contains("add_to_score"),
10891            "not granted, so not there:\n{}",
10892            without
10893        );
10894        // And with no mutation of any kind left, there is no mutation root --
10895        // which is what `no mutations exist` is answered from.
10896        assert!(
10897            !without.contains("mutation: mutation_root"),
10898            "and no mutation root at all:\n{}",
10899            without
10900        );
10901
10902        let with = sdl_for(&granted);
10903        assert!(
10904            with.contains("add_to_score"),
10905            "named by a function permission, so it is:\n{}",
10906            with
10907        );
10908    }
10909
10910    /// An unrecognised role is a role that was granted nothing, not an error.
10911    #[test]
10912    fn a_role_the_document_never_names_is_granted_nothing() {
10913        let cache = Arc::new(create_test_schema_cache());
10914        let config = SchemaConfig {
10915            names: crate::names::NameOverrides::parse(
10916                r#"{"tables": {"public.users": {"permissions":
10917                     {"reader": {"select": {"columns": "*", "filter": {}}}}}}}"#,
10918            )
10919            .unwrap(),
10920            ..SchemaConfig::default()
10921        };
10922        let empty = build_ungranted_schema(&cache, &config).expect("it builds");
10923        let sdl = empty.sdl();
10924        assert!(
10925            sdl.contains("no_queries_available"),
10926            "nothing to read:\n{}",
10927            sdl
10928        );
10929        assert!(!sdl.contains("type users "), "and no table:\n{}", sdl);
10930        // A document with no permissions at all has no unrecognised roles.
10931        assert!(build_ungranted_schema(&cache, &SchemaConfig::default()).is_none());
10932    }
10933
10934    /// A column the role may write and not read is in the input and not in the
10935    /// type. The two column sets are the point: one schema cache, two answers.
10936    #[test]
10937    fn a_column_a_role_may_set_without_seeing_is_in_the_input_alone() {
10938        let cache = create_test_schema_cache();
10939        let names = crate::names::NameOverrides::parse(
10940            r#"{"tables": {"public.users": {"permissions":
10941                 {"user": {"select": {"columns": ["id"], "filter": {}},
10942                           "insert": {"columns": "*", "check": {}}}}}}}"#,
10943        )
10944        .unwrap();
10945        let view = crate::role::cache_for_role(&cache, &names, "user", false);
10946        let config = SchemaConfig {
10947            names: names.clone(),
10948            role: Some("user".to_string()),
10949            ..SchemaConfig::default()
10950        };
10951        let generated = build_schema(&view, &config);
10952        let object = &generated.object_types["users"];
10953        assert_eq!(
10954            object
10955                .fields
10956                .iter()
10957                .map(|f| f.name.as_str())
10958                .collect::<Vec<_>>(),
10959            vec!["id"],
10960            "the type shows what may be read"
10961        );
10962        assert!(
10963            object.writable_fields.iter().any(|f| f.name == "name"),
10964            "and the input keeps what may be written"
10965        );
10966
10967        let schema = build_dynamic_schema(
10968            &generated,
10969            &view,
10970            None,
10971            None,
10972            Arc::new(names),
10973            std::time::Duration::from_secs(30),
10974            Some("user"),
10975        )
10976        .expect("schema builds");
10977        let sdl = schema.sdl();
10978        let input = sdl
10979            .split("input users_insert_input")
10980            .nth(1)
10981            .and_then(|rest| rest.split('}').next())
10982            .expect("the insert input is registered");
10983        assert!(input.contains("name:"), "settable without being readable");
10984    }
10985
10986    /// A column PostgreSQL generates always is in the row type and in no
10987    /// write input. It is not a permission -- no role may name it -- so the
10988    /// refusal belongs to the schema rather than to the database.
10989    #[test]
10990    fn a_generated_column_is_readable_and_not_writable() {
10991        let mut cache = create_test_schema_cache();
10992        cache
10993            .tables
10994            .get_mut(&postrust_core::api_request::QualifiedIdentifier::new(
10995                "public", "users",
10996            ))
10997            .expect("the fixture has users")
10998            .columns
10999            .get_mut("id")
11000            .expect("with an id")
11001            .always_generated = true;
11002
11003        let names = crate::names::NameOverrides::default();
11004        let config = SchemaConfig {
11005            names: names.clone(),
11006            ..SchemaConfig::default()
11007        };
11008        let generated = build_schema(&cache, &config);
11009        let schema = build_dynamic_schema(
11010            &generated,
11011            &cache,
11012            None,
11013            None,
11014            Arc::new(names),
11015            std::time::Duration::from_secs(30),
11016            None,
11017        )
11018        .expect("schema builds");
11019        let sdl = schema.sdl();
11020
11021        let block = |header: &str| {
11022            sdl.split(header)
11023                .nth(1)
11024                .and_then(|rest| rest.split('}').next())
11025                .unwrap_or_else(|| panic!("{} is registered:\n{}", header, sdl))
11026                .to_string()
11027        };
11028        assert!(
11029            block("type users ").contains("id:"),
11030            "still readable:\n{}",
11031            sdl
11032        );
11033        for header in ["input users_insert_input", "input users_set_input"] {
11034            assert!(
11035                !block(header).contains("id:"),
11036                "{} may not name it:\n{}",
11037                header,
11038                block(header)
11039            );
11040        }
11041        // The table's only number was the generated column, so there is
11042        // nothing left to add to and no type for adding to it.
11043        assert!(
11044            !sdl.contains("users_inc_input"),
11045            "and nothing is left to increment:\n{}",
11046            sdl
11047        );
11048        assert!(
11049            block("input users_insert_input").contains("name:"),
11050            "and the rest of the table is untouched",
11051        );
11052    }
11053
11054    #[test]
11055    fn test_create_object_type() {
11056        let table = create_test_table("users");
11057        let obj = TableObjectType::from_table(&table);
11058        let _gql_obj = create_object_type(&obj, &[], None);
11059    }
11060
11061    #[test]
11062    fn test_create_query_type() {
11063        let cache = create_test_schema_cache();
11064        let config = SchemaConfig::default();
11065        let generated = build_schema(&cache, &config);
11066
11067        let _query = create_query_type(
11068            &generated,
11069            "query_root",
11070            None,
11071            Arc::new(HashMap::new()),
11072            Arc::new(Default::default()),
11073        );
11074    }
11075
11076    #[test]
11077    fn test_create_mutation_type() {
11078        let cache = create_test_schema_cache();
11079        let config = SchemaConfig::default();
11080        let generated = build_schema(&cache, &config);
11081
11082        let _mutation = create_mutation_type(
11083            &generated,
11084            "mutation_root",
11085            Arc::new(HashMap::new()),
11086            Arc::new(HashMap::new()),
11087            Arc::new(Default::default()),
11088            None,
11089            None,
11090        );
11091    }
11092
11093    // ============================================================================
11094    // Scalar Tests
11095    // ============================================================================
11096
11097    #[test]
11098    fn a_scalar_is_named_the_way_a_client_declares_it() {
11099        use crate::types::{pg_type_to_graphql, GraphQLType};
11100
11101        // These names appear in the client's own queries -- `query ($x:
11102        // jsonb!)` names a type that has to exist under exactly that
11103        // spelling -- so they are asserted rather than left to the Display
11104        // impl.
11105        assert_eq!(pg_type_to_graphql("jsonb").to_string(), "jsonb");
11106        assert_eq!(pg_type_to_graphql("json").to_string(), "json");
11107        assert_eq!(pg_type_to_graphql("int8").to_string(), "bigint");
11108        assert_eq!(pg_type_to_graphql("numeric").to_string(), "numeric");
11109        assert_eq!(pg_type_to_graphql("timestamptz").to_string(), "timestamptz");
11110        assert_eq!(pg_type_to_graphql("timestamp").to_string(), "timestamp");
11111        assert_eq!(pg_type_to_graphql("uuid").to_string(), "uuid");
11112
11113        // A type this server knows nothing about keeps its own name, which is
11114        // how a PostGIS column and a database enum both become usable.
11115        assert_eq!(pg_type_to_graphql("geometry").to_string(), "geometry");
11116        assert_eq!(
11117            pg_type_to_graphql("colors_enum"),
11118            GraphQLType::Custom("colors_enum".to_string())
11119        );
11120
11121        // And the leaf of an array is what gets registered, not the array.
11122        assert_eq!(
11123            leaf_scalar_name(&pg_type_to_graphql("_text")),
11124            "String".to_string()
11125        );
11126    }
11127
11128    // ============================================================================
11129    // Filter Input Type Tests
11130    // ============================================================================
11131
11132    #[test]
11133    fn every_table_gets_a_boolean_expression() {
11134        let cache = create_test_schema_cache();
11135        let config = SchemaConfig::default();
11136        let generated = build_schema(&cache, &config);
11137
11138        let (inputs, _) = crate::input::bool_exp::build_inputs(
11139            &generated.object_types,
11140            &generated.relationship_fields,
11141        );
11142        let names: HashSet<String> = inputs.iter().map(|i| i.type_name().to_string()).collect();
11143
11144        for table in generated.object_types.keys() {
11145            let expected = format!("{}_bool_exp", table);
11146            assert!(
11147                names.contains(&expected),
11148                "no {} among {:?}",
11149                expected,
11150                names
11151            );
11152        }
11153        // The scalars the fixture's columns use.
11154        assert!(names.contains("String_comparison_exp"));
11155        assert!(names.contains("Int_comparison_exp"));
11156    }
11157
11158    #[test]
11159    fn a_schema_carrying_the_boolean_expressions_still_builds() {
11160        let cache = create_test_schema_cache();
11161        let config = SchemaConfig::default();
11162        let generated = build_schema(&cache, &config);
11163
11164        let schema = build_dynamic_schema(
11165            &generated,
11166            &cache,
11167            None,
11168            None,
11169            Arc::new(Default::default()),
11170            std::time::Duration::from_secs(30),
11171            None,
11172        );
11173        assert!(schema.is_ok(), "{:?}", schema.err());
11174
11175        let sdl = schema.unwrap().sdl();
11176        assert!(
11177            sdl.contains("_bool_exp"),
11178            "no boolean expressions in:\n{}",
11179            sdl
11180        );
11181        assert!(sdl.contains("_and"), "no _and in the generated expressions");
11182    }
11183
11184    // ============================================================================
11185    // Subscription Tests
11186    // ============================================================================
11187
11188    #[test]
11189    fn test_build_schema_with_subscriptions() {
11190        let cache = create_test_schema_cache();
11191        let config = SchemaConfig {
11192            enable_subscriptions: true,
11193            ..SchemaConfig::default()
11194        };
11195        let generated = build_schema(&cache, &config);
11196
11197        // Generate subscription fields
11198        let sub_fields = generate_subscription_fields(&cache, &generated);
11199        assert!(!sub_fields.is_empty(), "Should have subscription fields");
11200
11201        // Build schema with subscriptions
11202        let result = build_dynamic_schema(
11203            &generated,
11204            &cache,
11205            Some(&sub_fields),
11206            None,
11207            Arc::new(Default::default()),
11208            std::time::Duration::from_secs(30),
11209            None,
11210        );
11211        assert!(result.is_ok(), "Schema with subscriptions should build");
11212    }
11213
11214    #[test]
11215    fn test_subscription_field_generation() {
11216        let cache = create_test_schema_cache();
11217        let config = SchemaConfig::default();
11218        let generated = build_schema(&cache, &config);
11219
11220        let fields = generate_subscription_fields(&cache, &generated);
11221
11222        // Should have one subscription field for the users table
11223        assert_eq!(fields.len(), 1);
11224        assert_eq!(fields[0].name, "users");
11225        assert_eq!(fields[0].table_name, "users");
11226        assert_eq!(fields[0].channel_name(), "postrust_public_users");
11227    }
11228
11229    #[test]
11230    fn test_create_subscription_type() {
11231        let cache = create_test_schema_cache();
11232        let config = SchemaConfig::default();
11233        let generated = build_schema(&cache, &config);
11234        // A live query per queryable root: the subscription root mirrors the
11235        // query root, which is the whole contract.
11236        let _subscription = create_subscription_type(
11237            &generated,
11238            "subscription_root",
11239            None,
11240            Arc::new(Default::default()),
11241            Arc::new(Default::default()),
11242            std::time::Duration::from_secs(30),
11243        );
11244    }
11245}
11246
11247/// One step of a JSON path: a key of an object or an index of an array.
11248#[derive(Debug, PartialEq, Eq)]
11249enum PathStep {
11250    Key(String),
11251    Index(usize),
11252}
11253
11254/// Read one part of a document, by the path spelling Hasura accepts.
11255///
11256/// `column(path: "objs[0]['你好']")` -- an optional leading `$`, then keys
11257/// written bare or after a dot, and indices or quoted keys written in
11258/// brackets. A step that names something the document does not have is null,
11259/// which is what `#>` answers to the same path.
11260fn walk_json_path(value: &Value, path: &str) -> Result<Value, async_graphql::Error> {
11261    let mut at = value;
11262    for step in parse_json_path(path)? {
11263        let next = match (&step, at) {
11264            (PathStep::Key(key), Value::Object(map)) => map.get(key.as_str()),
11265            (PathStep::Index(index), Value::List(items)) => items.get(*index),
11266            // An index of an object or a key of an array reads nothing, and so
11267            // does anything asked of a scalar.
11268            _ => None,
11269        };
11270        match next {
11271            Some(next) => at = next,
11272            None => return Ok(Value::Null),
11273        }
11274    }
11275    Ok(at.clone())
11276}
11277
11278/// A path whose bracket never closes: written wrong rather than written oddly.
11279fn unclosed_bracket(path: &str) -> async_graphql::Error {
11280    let message = format!("\"{}\" is not a JSON path: a bracket is never closed", path);
11281    validation_error(&message)
11282}
11283
11284/// Parse the path spelling Hasura accepts into its steps.
11285///
11286/// Deliberately tolerant about separators, because the corpus writes the same
11287/// path four ways: `objs[0]["x"]`, `objs[0].["x"]`, `objs.[0].["x"]` and
11288/// `.objs[0]['x']` all name one thing. What is not tolerated is a bracket that
11289/// never closes, which is a path the client got wrong rather than a spelling.
11290fn parse_json_path(path: &str) -> Result<Vec<PathStep>, async_graphql::Error> {
11291    let mut steps = Vec::new();
11292    let mut chars = path.chars().peekable();
11293    // `$` is the document itself, and naming it is optional.
11294    if chars.peek() == Some(&'$') {
11295        chars.next();
11296    }
11297    while let Some(&c) = chars.peek() {
11298        match c {
11299            // A separator carries no step of its own.
11300            '.' => {
11301                chars.next();
11302            }
11303            '[' => {
11304                chars.next();
11305                let quote = match chars.peek() {
11306                    Some(&q @ ('\'' | '"')) => {
11307                        chars.next();
11308                        Some(q)
11309                    }
11310                    _ => None,
11311                };
11312                let mut inner = String::new();
11313                loop {
11314                    let Some(c) = chars.next() else {
11315                        return Err(unclosed_bracket(path));
11316                    };
11317                    match (quote, c) {
11318                        (Some(quote), c) if c == quote => break,
11319                        (None, ']') => break,
11320                        _ => inner.push(c),
11321                    }
11322                }
11323                // A quoted key's closing bracket, which the quote ended before.
11324                if quote.is_some() {
11325                    match chars.next() {
11326                        Some(']') => {}
11327                        _ => return Err(unclosed_bracket(path)),
11328                    }
11329                }
11330                // An unquoted number is an index; anything else is a key,
11331                // since an object may be keyed by a word as well as a digit.
11332                match (quote, inner.parse::<usize>()) {
11333                    (None, Ok(index)) => steps.push(PathStep::Index(index)),
11334                    _ => steps.push(PathStep::Key(inner)),
11335                }
11336            }
11337            _ => {
11338                let mut key = String::new();
11339                while let Some(&c) = chars.peek() {
11340                    if matches!(c, '.' | '[') {
11341                        break;
11342                    }
11343                    key.push(c);
11344                    chars.next();
11345                }
11346                steps.push(PathStep::Key(key));
11347            }
11348        }
11349    }
11350    Ok(steps)
11351}
11352
11353#[cfg(test)]
11354mod json_path_tests {
11355    use super::*;
11356
11357    fn steps(path: &str) -> Vec<PathStep> {
11358        parse_json_path(path).expect("the path parses")
11359    }
11360
11361    fn key(name: &str) -> PathStep {
11362        PathStep::Key(name.to_string())
11363    }
11364
11365    #[test]
11366    fn the_document_itself_has_no_steps() {
11367        assert_eq!(steps("$"), vec![]);
11368        assert_eq!(steps(""), vec![]);
11369    }
11370
11371    #[test]
11372    fn a_key_is_written_bare_or_after_a_dot() {
11373        assert_eq!(steps("a"), vec![key("a")]);
11374        assert_eq!(steps(".obj.c1"), vec![key("obj"), key("c1")]);
11375        assert_eq!(steps("._underscore"), vec![key("_underscore")]);
11376    }
11377
11378    #[test]
11379    fn a_bracket_holds_an_index_or_a_quoted_key() {
11380        assert_eq!(steps("arr[0]"), vec![key("arr"), PathStep::Index(0)]);
11381        assert_eq!(steps("['!@#$%^']"), vec![key("!@#$%^")]);
11382        assert_eq!(
11383            steps("translations['hello world!']"),
11384            vec![key("translations"), key("hello world!")]
11385        );
11386    }
11387
11388    /// The corpus writes one path four ways, and Hasura reads them all alike.
11389    #[test]
11390    fn a_dot_before_a_bracket_changes_nothing() {
11391        let expected = vec![key("objs"), PathStep::Index(0), key("\u{4f60}\u{597d}")];
11392        for spelling in [
11393            "objs[0]['\u{4f60}\u{597d}']",
11394            "objs[0][\"\u{4f60}\u{597d}\"]",
11395            "objs[0].[\"\u{4f60}\u{597d}\"]",
11396            "objs.[0].[\"\u{4f60}\u{597d}\"]",
11397        ] {
11398            assert_eq!(steps(spelling), expected, "{}", spelling);
11399        }
11400    }
11401
11402    #[test]
11403    fn a_bracket_that_never_closes_is_refused() {
11404        assert!(parse_json_path("arr[0").is_err());
11405        assert!(parse_json_path("['key'").is_err());
11406    }
11407
11408    #[test]
11409    fn a_step_the_document_does_not_have_reads_null() {
11410        let document = Value::from_json(serde_json::json!({
11411            "obj": {"c1": "c2"},
11412            "arr": [1, 2, 3],
11413        }))
11414        .expect("a document");
11415        let at = |path| walk_json_path(&document, path).expect("the path parses");
11416        assert_eq!(at(".obj.c1"), Value::String("c2".to_string()));
11417        assert_eq!(at("arr[1]"), Value::Number(2.into()));
11418        assert_eq!(at("arr[9]"), Value::Null);
11419        assert_eq!(at(".obj.nothing"), Value::Null);
11420        // An index of an object, and a key of an array: neither reads
11421        // anything, which is what `#>` answers.
11422        assert_eq!(at("obj[0]"), Value::Null);
11423        assert_eq!(at("arr.first"), Value::Null);
11424    }
11425}