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 wanted: BTreeMap<String, Vec<String>> = BTreeMap::new();
3806    // `count` is different: each occurrence may count something else, so each
3807    // is answered under the name it was asked for.
3808    let mut counts: Vec<(String, String)> = Vec::new();
3809    for selection in ctx.field().selection_set() {
3810        match selection.name() {
3811            "nodes" => wants_nodes = true,
3812            "aggregate" => {
3813                for function in selection.selection_set() {
3814                    if function.name() == "count" {
3815                        counts.push((
3816                            function.alias().unwrap_or("count").to_string(),
3817                            count_expression(
3818                                function,
3819                                (&spec.schema_name, &spec.table_name),
3820                                None,
3821                                spec.names.as_ref(),
3822                            ),
3823                        ));
3824                        continue;
3825                    }
3826                    let columns = wanted.entry(function.name().to_string()).or_default();
3827                    for column in function.selection_set() {
3828                        let name = column.name().to_string();
3829                        if !columns.contains(&name) {
3830                            columns.push(name);
3831                        }
3832                    }
3833                }
3834            }
3835            _ => {}
3836        }
3837    }
3838    wanted.retain(|_, columns| !columns.is_empty());
3839
3840    let mut result = async_graphql::indexmap::IndexMap::new();
3841
3842    if !counts.is_empty() || !wanted.is_empty() {
3843        // `count` is answered whether or not it was asked for: the aggregate
3844        // type's own resolver reads a missing key as zero, and a client that
3845        // asked for nothing else still gets a number rather than a null.
3846        let mut parts = match counts.iter().any(|(key, _)| key == "count") {
3847            true => Vec::new(),
3848            false => vec!["'count', count(*)".to_string()],
3849        };
3850        for (key, expression) in &counts {
3851            parts.push(format!("'{}', {}", key.replace('\'', "''"), expression));
3852        }
3853        for (function, columns) in &wanted {
3854            // The function name comes from the schema, not from the request:
3855            // a selection can only name a field that was generated, so there
3856            // is no path from client text to this identifier.
3857            let sql_function = agg::NUMERIC_AGGREGATES
3858                .iter()
3859                .chain(agg::ORDERED_AGGREGATES.iter())
3860                .find(|(name, _)| name == function)
3861                .map(|(name, _)| *name)
3862                .ok_or_else(|| {
3863                    async_graphql::Error::new(format!("unknown aggregate \"{}\"", function))
3864                })?;
3865            let per_column: Vec<String> = columns
3866                .iter()
3867                .map(|field| {
3868                    let source = spec
3869                        .names
3870                        .column_source(&spec.schema_name, &spec.table_name, field)
3871                        .unwrap_or(field);
3872                    format!(
3873                        "'{}', {}({})",
3874                        field.replace('\'', "''"),
3875                        sql_function,
3876                        postrust_sql::escape_ident(source)
3877                    )
3878                })
3879                .collect();
3880            parts.push(format!(
3881                "'{}', json_build_object({})",
3882                sql_function,
3883                per_column.join(", ")
3884            ));
3885        }
3886
3887        let sql = format!(
3888            "SELECT json_build_object({}) FROM ({}) AS pgrst_agg",
3889            parts.join(", "),
3890            counted
3891        );
3892        let mut conn =
3893            begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3894        let rows = execute_query_on(&mut conn, &sql, &bound_values).await?;
3895        conn.commit().await?;
3896        if let Some(first) = rows.into_iter().next() {
3897            if let Value::Object(map) = json_to_value(first) {
3898                result.insert(async_graphql::Name::new("aggregate"), Value::Object(map));
3899            }
3900        }
3901    }
3902
3903    if wants_nodes {
3904        // `nodes` is the same rows selection the plain root field answers, so
3905        // it gets the same projection: computed fields, which are functions of
3906        // the row rather than part of it, and relationships, which are
3907        // correlated subselects. Reading it as columns alone was why a
3908        // relationship asked for beside a count came back null.
3909        let nodes = ctx
3910            .field()
3911            .selection_set()
3912            .find(|selection| selection.name() == "nodes")
3913            .ok_or_else(|| async_graphql::Error::new("nodes was asked for and then was not"))?;
3914
3915        // The aggregate query has already been sent, so its parameters are
3916        // fixed; anything an embed binds continues that numbering in a vector
3917        // of its own.
3918        let mut node_values = bound_values.clone();
3919        let (projection, row_column_types) = {
3920            let guard = gql_ctx
3921                .schema_cache
3922                .get()
3923                .await
3924                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3925            let cache = guard
3926                .as_ref()
3927                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3928            let qi = postrust_core::api_request::QualifiedIdentifier::new(
3929                &spec.schema_name,
3930                &spec.table_name,
3931            );
3932            let table = cache.get_table(&qi);
3933            let mut param_idx = node_values.len() + 1;
3934            let computed = match table {
3935                Some(table) => computed_projections(
3936                    table,
3937                    nodes.selection_set(),
3938                    "src",
3939                    spec.names.as_ref(),
3940                    cache,
3941                    &mut param_idx,
3942                    &mut node_values,
3943                )?,
3944                None => Vec::new(),
3945            };
3946            let embeds = build_embed_expressions(
3947                &gql_ctx.caller(),
3948                cache,
3949                spec.relationships.as_ref(),
3950                &spec.type_name,
3951                "src",
3952                nodes.selection_set(),
3953                spec.max_rows,
3954                &mut 0,
3955                &mut param_idx,
3956                &mut node_values,
3957                spec.names.as_ref(),
3958            )?;
3959            // A renamed column is renamed here, in the projection over the
3960            // subquery -- not inside it, where `src` has to stay the table's
3961            // own composite for a computed field to be passed the row.
3962            let mut projection = table
3963                .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
3964                .unwrap_or_else(|| "src.*".to_string());
3965            for expression in &computed {
3966                projection.push_str(", ");
3967                projection.push_str(expression);
3968            }
3969            for (field_name, expression) in &embeds {
3970                projection.push_str(", ");
3971                projection.push_str(expression);
3972                projection.push_str(" AS ");
3973                projection.push_str(&postrust_sql::escape_ident(field_name));
3974            }
3975            (
3976                projection,
3977                table
3978                    .map(|table| exposed_column_types(table, spec.names.as_ref()))
3979                    .unwrap_or_default(),
3980            )
3981        };
3982
3983        let sql = format!(
3984            "SELECT {} FROM (SELECT {} FROM ({}) AS src) AS pgrst_nodes",
3985            row_json("pgrst_nodes", &row_column_types),
3986            projection,
3987            inner
3988        );
3989        let mut conn =
3990            begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3991        let rows = execute_query_on(&mut conn, &sql, &node_values).await?;
3992        conn.commit().await?;
3993        result.insert(
3994            async_graphql::Name::new("nodes"),
3995            Value::List(rows.into_iter().map(json_to_value).collect()),
3996        );
3997    }
3998
3999    let _ = &spec.type_name;
4000    Ok(Value::Object(result))
4001}
4002
4003/// Resolve a query field.
4004async fn resolve_query<'a>(
4005    ctx: &ResolverContext<'a>,
4006    spec: &QueryFieldSpec,
4007) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4008    Ok(rows_as_field_value(
4009        query_rows(ctx, spec).await?,
4010        spec.is_by_pk,
4011    ))
4012}
4013
4014/// The rows a query field answers with, as JSON.
4015///
4016/// Split from the field's own resolver because a subscription reads the same
4017/// rows over and over and has to tell one answer from the last: a
4018/// `FieldValue` is neither comparable nor readable, and this is.
4019async fn query_rows(
4020    ctx: &ResolverContext<'_>,
4021    spec: &QueryFieldSpec,
4022) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4023    let schema_name = spec.schema_name.as_str();
4024    let table_name = spec.table_name.as_str();
4025    let type_name = spec.type_name.as_str();
4026    let is_by_pk = spec.is_by_pk;
4027    let pk_columns = spec.pk_columns.as_slice();
4028    let max_rows = spec.max_rows;
4029    let relationships = spec.relationships.as_ref();
4030
4031    let pool = ctx.data::<PgPool>()?;
4032    let gql_ctx = ctx.data::<GraphQLContext>()?;
4033
4034    debug!("Resolving query for table: {}", table_name);
4035
4036    // Extract pagination arguments
4037    let requested_limit: Option<i64> = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
4038
4039    let offset: Option<i64> = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
4040
4041    // A query that names no limit would otherwise select the whole table, so
4042    // the configured ceiling is applied as the limit in that case, and as an
4043    // upper bound when the query asks for more than it. A by-PK query resolves
4044    // to at most one row.
4045    let limit: Option<i64> = if is_by_pk {
4046        Some(1)
4047    } else {
4048        // The permission's ceiling is one more of the same kind, so it folds
4049        // in the same way: whichever of the three is smallest wins, and a
4050        // request naming none takes the lowest that was named.
4051        let ceiling = match (
4052            max_rows,
4053            permission_limit(
4054                &gql_ctx.caller(),
4055                spec.names.as_ref(),
4056                schema_name,
4057                table_name,
4058            ),
4059        ) {
4060            (Some(configured), Some(granted)) => Some(configured.min(granted)),
4061            (only, None) | (None, only) => only,
4062        };
4063        match (requested_limit, ceiling) {
4064            (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
4065            (Some(requested), None) => Some(requested),
4066            (None, ceiling) => ceiling,
4067        }
4068    };
4069
4070    // Build the WHERE clause.
4071    //
4072    // A by-PK query filters on the table's key columns; each value is bound as
4073    // a parameter and cast to the column's type, since GraphQL scalars and
4074    // PostgreSQL types do not line up (a `uuid` key arrives as a String). A
4075    // list query filters on the `filter` argument, which takes the same shape
4076    // as a mutation's `where`.
4077    let mut where_sql = String::new();
4078    let mut bound_values: Vec<serde_json::Value> = Vec::new();
4079
4080    // Where the rows come from. A function's own arguments are bound before
4081    // anything else, because they sit in the FROM clause and every other
4082    // parameter is numbered after them.
4083    let source = match &spec.call {
4084        // Aliased rather than named. A whole-row reference -- what a computed
4085        // field is passed -- can only be written as a bare name, and
4086        // `"public"."author"` is not one: PostgreSQL reads it as a column of a
4087        // table called `public`. An alias no column can share is a name that
4088        // works in both positions.
4089        None => format!(
4090            "{}.{} AS {}",
4091            postrust_sql::escape_ident(schema_name),
4092            postrust_sql::escape_ident(table_name),
4093            postrust_sql::escape_ident(READ_ROW)
4094        ),
4095        Some(call) => function_source(ctx, call, table_name, &mut bound_values)?,
4096    };
4097
4098    // How a column of the source is referred to: a table by its qualified
4099    // name, a function's rows by the alias above.
4100    let source_ref = match &spec.call {
4101        None => postrust_sql::escape_ident(READ_ROW),
4102        Some(_) => postrust_sql::escape_ident(table_name),
4103    };
4104
4105    if is_by_pk {
4106        if pk_columns.is_empty() {
4107            return Err(async_graphql::Error::new(format!(
4108                "\"{}\" has no primary key, so it cannot be queried by key",
4109                table_name
4110            )));
4111        }
4112
4113        let mut conditions = Vec::with_capacity(pk_columns.len());
4114        for (idx, (col_name, pg_type)) in pk_columns.iter().enumerate() {
4115            let field = spec
4116                .names
4117                .column(schema_name, table_name, col_name)
4118                .unwrap_or(col_name);
4119            let value = ctx.args.try_get(field).map_err(|_| {
4120                async_graphql::Error::new(format!(
4121                    "missing required primary key argument \"{}\"",
4122                    field
4123                ))
4124            })?;
4125
4126            conditions.push(format!(
4127                "{} = ${}::{}",
4128                postrust_sql::escape_ident(col_name),
4129                idx + 1,
4130                pg_type
4131            ));
4132            bound_values.push(accessor_to_json(&value));
4133        }
4134        where_sql = format!(" WHERE {}", conditions.join(" AND "));
4135    }
4136
4137    // What this role may read, beside what the request asked for. Applied to
4138    // both shapes and to neither: a query naming no `where` at all is still a
4139    // read, and a by-key query that skipped the permission would be the one
4140    // way to fetch a row the filter withholds.
4141    {
4142        let requested = match is_by_pk {
4143            true => None,
4144            false => ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v)),
4145        };
4146        let permission = permission_predicate(
4147            &gql_ctx.caller(),
4148            spec.names.as_ref(),
4149            schema_name,
4150            table_name,
4151        )?;
4152
4153        if let Some(predicate) = and_predicates(requested, permission) {
4154            let guard = gql_ctx
4155                .schema_cache
4156                .get()
4157                .await
4158                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4159            let cache = guard
4160                .as_ref()
4161                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4162            let scope = match &spec.call {
4163                None => WhereScope::table(schema_name, table_name, type_name, spec.names.as_ref())
4164                    .under_alias(READ_ROW)
4165                    .with_resolution(cache, relationships)
4166                    .for_caller(gql_ctx.caller()),
4167                // A function's rows are the table's rows, so they are renamed
4168                // the same way; only how they are reached differs.
4169                Some(_) => WhereScope::for_alias(
4170                    schema_name,
4171                    table_name,
4172                    table_name,
4173                    type_name,
4174                    cache,
4175                    relationships,
4176                    spec.names.as_ref(),
4177                )
4178                .for_caller(gql_ctx.caller()),
4179            };
4180            let (filter_sql, filter_values) =
4181                build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
4182            if !filter_sql.is_empty() {
4183                // A by-key query already has a `WHERE`, so this joins it
4184                // rather than replacing it.
4185                where_sql = match where_sql.is_empty() {
4186                    true => format!(" {}", filter_sql),
4187                    false => format!(
4188                        "{} AND ({})",
4189                        where_sql,
4190                        filter_sql.trim_start_matches("WHERE ")
4191                    ),
4192                };
4193                bound_values.extend(filter_values);
4194            }
4195        }
4196    }
4197
4198    // A by-key query resolves to one row, so neither ordering nor distinct
4199    // has anything to do.
4200    let (order_sql, distinct_on) = if is_by_pk {
4201        (String::new(), Vec::new())
4202    } else {
4203        (
4204            build_order_by_clause(
4205                ctx,
4206                &gql_ctx.schema_cache,
4207                schema_name,
4208                table_name,
4209                type_name,
4210                relationships,
4211                &source_ref,
4212                spec.names.as_ref(),
4213            )
4214            .await?,
4215            build_distinct_on(
4216                ctx,
4217                &gql_ctx.schema_cache,
4218                schema_name,
4219                table_name,
4220                spec.names.as_ref(),
4221            )
4222            .await?,
4223        )
4224    };
4225
4226    // PostgreSQL keeps the first row of each DISTINCT ON group in the query's
4227    // own order, and picks arbitrarily where the ordering does not begin with
4228    // the distinct columns -- so which row survives would depend on the plan.
4229    // Hasura refuses that query rather than answering it, and so does this:
4230    // prepending the distinct columns instead produced an answer, and a wrong
4231    // one, since `ORDER BY "department", "department" DESC` is decided by its
4232    // first term and sorts ascending.
4233    let (distinct_sql, order_sql) = {
4234        let written = order_sql.strip_prefix(" ORDER BY ");
4235        // Reported against the field's arguments as a whole: the rule is about
4236        // two of them disagreeing, so neither one is where it went wrong.
4237        let (prefix, order) = distinct_on_clause(&distinct_on, written).map_err(|error| {
4238            at_path(
4239                error,
4240                &format!(
4241                    "$.selectionSet.{}.args",
4242                    ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4243                ),
4244            )
4245        })?;
4246        (
4247            prefix,
4248            match order {
4249                Some(order) => format!(" ORDER BY {}", order),
4250                None => String::new(),
4251            },
4252        )
4253    };
4254
4255    // A computed column is a function of the row rather than part of it, so
4256    // it is not in `*` and is named only when it was asked for.
4257    let (computed, row_column_types) = {
4258        let guard = gql_ctx
4259            .schema_cache
4260            .get()
4261            .await
4262            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4263        match guard.as_ref() {
4264            Some(cache) => {
4265                let qi =
4266                    postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
4267                match cache.get_table(&qi) {
4268                    Some(table) => {
4269                        let mut param_idx = bound_values.len() + 1;
4270                        (
4271                            computed_projections(
4272                                table,
4273                                ctx.field().selection_set(),
4274                                "src",
4275                                spec.names.as_ref(),
4276                                cache,
4277                                &mut param_idx,
4278                                &mut bound_values,
4279                            )?,
4280                            exposed_column_types(table, spec.names.as_ref()),
4281                        )
4282                    }
4283                    None => (Vec::new(), HashMap::new()),
4284                }
4285            }
4286            None => (Vec::new(), HashMap::new()),
4287        }
4288    };
4289    // ORDER BY, LIMIT and OFFSET belong inside the subquery: applying them to
4290    // the outer `row_to_json` projection would leave the ordering of the rows
4291    // that survive the limit unspecified.
4292    let mut inner = format!(
4293        "SELECT {}* FROM {}{}{}",
4294        distinct_sql, source, where_sql, order_sql
4295    );
4296
4297    if let Some(limit) = limit {
4298        inner.push_str(&format!(" LIMIT {}", limit));
4299    }
4300
4301    if let Some(offset) = offset {
4302        inner.push_str(&format!(" OFFSET {}", offset));
4303    }
4304
4305    // Embed the requested relationships in this same query, as correlated
4306    // subselects in the SELECT list, so the whole selection is one round trip.
4307    let embed_expressions = {
4308        let guard = gql_ctx
4309            .schema_cache
4310            .get()
4311            .await
4312            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4313        match guard.as_ref() {
4314            Some(cache) => {
4315                // Parameters bound inside an embed continue the outer query's
4316                // numbering: sqlx binds by position, so the values only have
4317                // to be pushed in the order their placeholders were handed
4318                // out.
4319                let mut param_idx = bound_values.len() + 1;
4320                build_embed_expressions(
4321                    &gql_ctx.caller(),
4322                    cache,
4323                    relationships,
4324                    type_name,
4325                    "src",
4326                    ctx.field().selection_set(),
4327                    max_rows,
4328                    &mut 0,
4329                    &mut param_idx,
4330                    &mut bound_values,
4331                    spec.names.as_ref(),
4332                )?
4333            }
4334            None => Vec::new(),
4335        }
4336    };
4337
4338    // A computed column is a function of the row, and the row it is a function
4339    // of is the table's -- not the subquery's. Projecting it inside the
4340    // subquery gave `src` an extra column, which is what made a computed
4341    // relationship beside it fail with `cannot cast type record to author`: a
4342    // subquery alias is only passable as a composite value while its columns
4343    // are exactly the table's. So both live out here, where `src` still is
4344    // one.
4345    let renamed = {
4346        let guard = gql_ctx
4347            .schema_cache
4348            .get()
4349            .await
4350            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4351        guard
4352            .as_ref()
4353            .and_then(|cache| {
4354                cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
4355                    schema_name,
4356                    table_name,
4357                ))
4358            })
4359            .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4360    };
4361    let inner = if embed_expressions.is_empty() && computed.is_empty() && renamed.is_none() {
4362        inner
4363    } else {
4364        let mut projection = renamed.unwrap_or_else(|| "src.*".to_string());
4365        for expression in &computed {
4366            projection.push_str(", ");
4367            projection.push_str(expression);
4368        }
4369        for (field_name, expression) in &embed_expressions {
4370            projection.push_str(", ");
4371            projection.push_str(expression);
4372            projection.push_str(" AS ");
4373            projection.push_str(&postrust_sql::escape_ident(field_name));
4374        }
4375        format!("SELECT {} FROM ({}) AS src", projection, inner)
4376    };
4377
4378    let sql = format!(
4379        "SELECT {} FROM ({}) t",
4380        row_json("t", &row_column_types),
4381        inner
4382    );
4383
4384    // One transaction for the query and any embeds hanging off it, so the role
4385    // applies to all of them and the parent and child rows come from a single
4386    // snapshot.
4387    let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
4388
4389    // Execute query - returns Vec<serde_json::Value>
4390    let mut result = execute_query_on(&mut tx, &sql, &bound_values).await?;
4391
4392    // Anything the single-query form did not cover.
4393    if embed_expressions.is_empty() {
4394        let guard = gql_ctx
4395            .schema_cache
4396            .get()
4397            .await
4398            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4399        let cache = guard
4400            .as_ref()
4401            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4402
4403        let embed_ctx = EmbedContext {
4404            schema_cache: cache,
4405            relationships,
4406            max_rows,
4407        };
4408
4409        embed_relationships(&mut tx, &embed_ctx, type_name, ctx.field(), &mut result).await?;
4410    }
4411
4412    tx.commit().await?;
4413
4414    Ok(result)
4415}
4416
4417/// The rows a field answers with, in the shape its type says.
4418///
4419/// A by-key field is one row or none; every other is a list, empty where
4420/// nothing matched rather than null.
4421fn rows_as_field_value<'a>(rows: Vec<serde_json::Value>, is_by_pk: bool) -> Option<FieldValue<'a>> {
4422    match is_by_pk {
4423        true => rows
4424            .into_iter()
4425            .next()
4426            .map(|v| FieldValue::value(json_to_value(v))),
4427        false => Some(FieldValue::list(
4428            rows.into_iter()
4429                .map(|v| FieldValue::value(json_to_value(v))),
4430        )),
4431    }
4432}
4433
4434/// Resolve a mutation field.
4435#[allow(clippy::too_many_arguments)]
4436async fn resolve_mutation<'a>(
4437    ctx: &ResolverContext<'a>,
4438    schema_name: &str,
4439    table_name: &str,
4440    mutation_type: MutationType,
4441    pk_columns: &[(String, String)],
4442    relationships: &Arc<HashMap<String, Vec<RelationshipField>>>,
4443    type_names: &Arc<HashMap<(String, String), String>>,
4444    names: &Arc<crate::names::NameOverrides>,
4445    max_rows: Option<i64>,
4446) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4447    let pool = ctx.data::<PgPool>()?;
4448    let gql_ctx = ctx.data::<GraphQLContext>()?;
4449
4450    debug!(
4451        "Resolving mutation for table: {} type: {:?}",
4452        table_name, mutation_type
4453    );
4454
4455    // A relationship or a computed field asked for beside the written columns
4456    // is not in `RETURNING`, so the rows are read again through the projection
4457    // an ordinary query uses -- and only when the selection asks for something
4458    // that needs it. A delete cannot do that afterwards and builds it into the
4459    // statement instead, which is why this is settled before the statement is.
4460    let type_name = type_names
4461        .get(&(schema_name.to_string(), table_name.to_string()))
4462        .cloned()
4463        .unwrap_or_else(|| table_name.to_string());
4464
4465    let by_key = matches!(
4466        mutation_type,
4467        MutationType::InsertOne | MutationType::UpdateByPk | MutationType::DeleteByPk
4468    );
4469    let returning = if by_key {
4470        Some(ctx.field())
4471    } else {
4472        ctx.field()
4473            .selection_set()
4474            .find(|selection| selection.name() == "returning")
4475    };
4476
4477    // Several updates, each with its own filter, applied in the order given.
4478    // They share the operation's transaction like any other write, which is
4479    // the whole difference from sending them one at a time: either all of them
4480    // happened or none did.
4481    if mutation_type == MutationType::UpdateMany {
4482        let updates = ctx
4483            .args
4484            .try_get("updates")
4485            .ok()
4486            .map(|v| accessor_to_json(&v))
4487            .unwrap_or(serde_json::Value::Null);
4488        let serde_json::Value::Array(entries) = updates else {
4489            return Err(async_graphql::Error::new(
4490                "\"updates\" takes a list of {where, _set, …} objects",
4491            ));
4492        };
4493        let column_types = column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await;
4494        let mut answers: Vec<FieldValue<'_>> = Vec::with_capacity(entries.len());
4495        for entry in entries {
4496            let serde_json::Value::Object(entry) = entry else {
4497                return Err(async_graphql::Error::new(
4498                    "each update is an object of {where, _set, …}",
4499                ));
4500            };
4501            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4502                .iter()
4503                .filter_map(|name| entry.get(*name).map(|value| (*name, value.clone())))
4504                .filter(|(_, value)| !value.is_null())
4505                .collect();
4506            let rows = execute_update(
4507                pool,
4508                gql_ctx,
4509                schema_name,
4510                table_name,
4511                &type_name,
4512                operators,
4513                column_types.clone(),
4514                entry.get("where").cloned(),
4515                relationships.as_ref(),
4516                names.as_ref(),
4517            )
4518            .await?;
4519            let affected = rows.len();
4520            let rows = match returning {
4521                Some(returning) if !rows.is_empty() => {
4522                    reread_returning(
4523                        pool,
4524                        gql_ctx,
4525                        schema_name,
4526                        table_name,
4527                        &type_name,
4528                        rows,
4529                        returning,
4530                        relationships.as_ref(),
4531                        names.as_ref(),
4532                        max_rows,
4533                    )
4534                    .await?
4535                }
4536                _ => rows,
4537            };
4538            answers.extend(mutation_result(rows, affected, false));
4539        }
4540        return Ok(Some(FieldValue::list(answers)));
4541    }
4542
4543    let (result, affected) = match mutation_type {
4544        MutationType::Insert | MutationType::InsertOne => {
4545            let objects = ctx
4546                .args
4547                .try_get("objects")
4548                .ok()
4549                .map(|v| accessor_to_json(&v))
4550                .or_else(|| {
4551                    // `insert_x_one(object: {...})` is one row spelled without
4552                    // the list.
4553                    ctx.args
4554                        .try_get("object")
4555                        .ok()
4556                        .map(|v| serde_json::Value::Array(vec![accessor_to_json(&v)]))
4557                })
4558                .unwrap_or_else(|| serde_json::Value::Array(vec![]));
4559
4560            let guard = gql_ctx
4561                .schema_cache
4562                .get()
4563                .await
4564                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4565            let cache = guard
4566                .as_ref()
4567                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4568            // Where this write is, in the document the client sent. The
4569            // field as it was written -- an alias if there was one, since that
4570            // is what names this selection.
4571            let here = format!(
4572                "$.selectionSet.{}",
4573                ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4574            );
4575            // `insert_x(objects: [...])` against `insert_x_one(object: {...})`:
4576            // the same write under two spellings, and the path says which.
4577            let named = match mutation_type {
4578                MutationType::InsertOne => "object",
4579                _ => "objects",
4580            };
4581            let context = InsertContext {
4582                on_conflict: ctx
4583                    .args
4584                    .try_get("on_conflict")
4585                    .ok()
4586                    .map(|v| accessor_to_json(&v))
4587                    .filter(|v| !v.is_null()),
4588                cache,
4589                relationships: relationships.as_ref(),
4590                type_names: type_names.as_ref(),
4591                names: names.as_ref(),
4592                caller: gql_ctx.caller(),
4593                // Filled in per row by `execute_insert`, which is where the
4594                // index is known.
4595                row: String::new(),
4596                objects: format!("{}.args.{}", here, named),
4597                conflict: format!("{}.args.on_conflict", here),
4598            };
4599
4600            execute_insert(
4601                pool,
4602                gql_ctx,
4603                schema_name,
4604                table_name,
4605                objects,
4606                &context,
4607                mutation_type == MutationType::InsertOne,
4608            )
4609            .await?
4610        }
4611        // `UpdateMany` answered above: it is a list of responses rather than
4612        // one, so it does not share this shape.
4613        MutationType::UpdateMany | MutationType::Update | MutationType::UpdateByPk => {
4614            // `_set` replaces, the others read the column they write. A client
4615            // may send more than one, so all of them are collected rather than
4616            // the first that happens to be present.
4617            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4618                .iter()
4619                .filter_map(|name| {
4620                    ctx.args
4621                        .try_get(name)
4622                        .ok()
4623                        .map(|v| (*name, accessor_to_json(&v)))
4624                })
4625                .filter(|(_, value)| !value.is_null())
4626                .collect();
4627
4628            let where_clause = if mutation_type == MutationType::UpdateByPk {
4629                Some(pk_where_from_args(
4630                    ctx,
4631                    schema_name,
4632                    table_name,
4633                    pk_columns,
4634                    names.as_ref(),
4635                )?)
4636            } else {
4637                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4638            };
4639            // Which rows this role may change, beside which rows the request
4640            // asked to change. A by-key update is narrowed too: naming the key
4641            // of a row the permission withholds must not be a way to reach it.
4642            let where_clause = and_predicates(
4643                where_clause,
4644                permission_filter(
4645                    &gql_ctx.caller(),
4646                    names.as_ref(),
4647                    schema_name,
4648                    table_name,
4649                    crate::role::Verb::Update,
4650                )?,
4651            );
4652
4653            execute_update(
4654                pool,
4655                gql_ctx,
4656                schema_name,
4657                table_name,
4658                &type_name,
4659                operators,
4660                column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await,
4661                where_clause,
4662                relationships.as_ref(),
4663                names.as_ref(),
4664            )
4665            .await
4666            .map(|rows| {
4667                let count = rows.len();
4668                (rows, count)
4669            })?
4670        }
4671        MutationType::Delete | MutationType::DeleteByPk => {
4672            let where_clause = if mutation_type == MutationType::DeleteByPk {
4673                Some(pk_where_from_args(
4674                    ctx,
4675                    schema_name,
4676                    table_name,
4677                    pk_columns,
4678                    names.as_ref(),
4679                )?)
4680            } else {
4681                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4682            };
4683            let where_clause = and_predicates(
4684                where_clause,
4685                permission_filter(
4686                    &gql_ctx.caller(),
4687                    names.as_ref(),
4688                    schema_name,
4689                    table_name,
4690                    crate::role::Verb::Delete,
4691                )?,
4692            );
4693
4694            execute_delete(
4695                pool,
4696                gql_ctx,
4697                schema_name,
4698                table_name,
4699                &type_name,
4700                where_clause,
4701                returning,
4702                relationships.as_ref(),
4703                names.as_ref(),
4704                max_rows,
4705            )
4706            .await
4707            .map(|rows| {
4708                let count = rows.len();
4709                (rows, count)
4710            })?
4711        }
4712    };
4713
4714    let is_delete = matches!(
4715        mutation_type,
4716        MutationType::Delete | MutationType::DeleteByPk
4717    );
4718    let result = match returning {
4719        Some(returning) if !result.is_empty() && !is_delete => {
4720            reread_returning(
4721                pool,
4722                gql_ctx,
4723                schema_name,
4724                table_name,
4725                &type_name,
4726                result,
4727                returning,
4728                relationships.as_ref(),
4729                names.as_ref(),
4730                max_rows,
4731            )
4732            .await?
4733        }
4734        _ => result,
4735    };
4736
4737    Ok(mutation_result(result, affected, by_key))
4738}
4739
4740/// The transaction this operation's writes share, opening it if this is the
4741/// first one.
4742///
4743/// Held for the length of the write, and settled by whoever answers the
4744/// request -- see [`crate::context::SharedWrite`]. Mutation root fields are
4745/// resolved one after another, so the lock is never contended; it is what
4746/// makes the transaction reachable from a resolver that owns nothing.
4747async fn write_tx<'a>(
4748    gql_ctx: &'a GraphQLContext,
4749    pool: &PgPool,
4750) -> Result<
4751    tokio::sync::MutexGuard<'a, Option<sqlx::Transaction<'static, sqlx::Postgres>>>,
4752    async_graphql::Error,
4753> {
4754    let mut guard = gql_ctx.write.lock().await;
4755    if guard.is_none() {
4756        *guard = Some(begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?);
4757    }
4758    Ok(guard)
4759}
4760
4761/// Begin a transaction with the request's role and session variables applied.
4762///
4763/// The settings go in before the role does. A role with fewer privileges may
4764/// not be allowed to call `set_config` at all, and a policy that reads a
4765/// setting the caller could then change is not a policy.
4766async fn begin_with_session(
4767    pool: &PgPool,
4768    role: &str,
4769    settings: &[(String, String)],
4770) -> Result<sqlx::Transaction<'static, sqlx::Postgres>, async_graphql::Error> {
4771    let mut tx = pool.begin().await?;
4772
4773    // One statement for all of them, which is what the REST path does. A loop
4774    // here costs a round trip per session variable, and there is always at
4775    // least one: `hasura.session` and `hasura.role` are set on every request,
4776    // so even an anonymous read paid two. An authenticated caller with six
4777    // `x-hasura-*` variables paid eight.
4778    //
4779    // The values stay bound rather than interpolated. `set_config` takes both
4780    // as arguments, where `SET LOCAL` would need the value written into the
4781    // statement text.
4782    if !settings.is_empty() {
4783        let calls = (0..settings.len())
4784            .map(|i| format!("set_config(${}, ${}, true)", i * 2 + 1, i * 2 + 2))
4785            .collect::<Vec<_>>()
4786            .join(", ");
4787        let sql = format!("SELECT {calls}");
4788        let mut query = sqlx::query_scalar::<_, String>(&sql);
4789        for (name, value) in settings {
4790            query = query.bind(name).bind(value);
4791        }
4792        query.fetch_optional(&mut *tx).await?;
4793    }
4794
4795    sqlx::query(&format!(
4796        "SET LOCAL ROLE {}",
4797        postrust_sql::escape_ident(role)
4798    ))
4799    .execute(&mut *tx)
4800    .await?;
4801    Ok(tx)
4802}
4803
4804/// Render a row as JSON, with any shape column rendered as GeoJSON.
4805///
4806/// PostgreSQL's text form for a geometry is WKB hex --
4807/// `0103000020E6100000…` -- which is what `row_to_json` puts in the response
4808/// and is not what any client can read. Hasura answers GeoJSON, and a client
4809/// migrating from it parses GeoJSON.
4810///
4811/// The shape columns are merged over the row rather than replacing the
4812/// projection, so a table with no shape column produces exactly the SQL it did
4813/// before and one with three needs no list of the others.
4814fn row_json(expr: &str, column_types: &HashMap<String, String>) -> String {
4815    let mut shapes: Vec<(&String, &String)> = column_types
4816        .iter()
4817        .filter(|(_, pg_type)| matches!(pg_type.as_str(), "geometry" | "geography"))
4818        .collect();
4819    if shapes.is_empty() {
4820        return format!("row_to_json({})", expr);
4821    }
4822    shapes.sort();
4823
4824    let overrides: Vec<String> = shapes
4825        .iter()
4826        .map(|(name, _)| {
4827            format!(
4828                // Option 4 asks PostGIS for the long-form CRS member --
4829                // `urn:ogc:def:crs:EPSG::4326` -- which Hasura includes and
4830                // which a client round-tripping a shape needs in order to
4831                // write it back where it came from. PostGIS emits it only
4832                // where the geometry has an SRID to report.
4833                "'{}', ST_AsGeoJSON({}.{}, 9, 4)::jsonb",
4834                name.replace('\'', "''"),
4835                expr,
4836                postrust_sql::escape_ident(name)
4837            )
4838        })
4839        .collect();
4840    format!(
4841        "(to_jsonb({}) || jsonb_build_object({}))",
4842        expr,
4843        overrides.join(", ")
4844    )
4845}
4846
4847/// Execute a SQL query and return results as serde_json::Value.
4848/// We keep data as serde_json::Value so field resolvers can use try_downcast_ref.
4849async fn execute_query_on(
4850    conn: &mut sqlx::PgConnection,
4851    sql: &str,
4852    params: &[serde_json::Value],
4853) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4854    use sqlx::Row;
4855
4856    trace!("Executing SQL: {}", sql);
4857
4858    // Execute query
4859    let mut query = sqlx::query(sql);
4860    for param in params {
4861        query = bind_json_value(query, param);
4862    }
4863    let rows = query.fetch_all(&mut *conn).await.map_err(database_error)?;
4864
4865    // Return raw JSON values - don't convert to async_graphql::Value
4866    // This allows field resolvers to use try_downcast_ref::<serde_json::Value>()
4867    let results: Vec<serde_json::Value> = rows
4868        .into_iter()
4869        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
4870        .collect();
4871
4872    Ok(results)
4873}
4874
4875/// Refuse a GeoJSON document PostGIS would accept and should not.
4876///
4877/// `ST_GeomFromGeoJSON` will build a "Polygon" from three points that do not
4878/// meet, and a "LineString" from one point -- neither of which is a shape, and
4879/// both of which are then stored and read back as though they were. Hasura
4880/// checks the document before it reaches the database, refuses with
4881/// `parse-failed`, and says which rule was broken; the messages here are its
4882/// messages, because a client that reports them to a user is reporting text it
4883/// already ships.
4884///
4885/// A value that is not an object is left alone: a string in a geometry column
4886/// is WKT or WKB hex, which is a different thing entirely.
4887fn check_geojson(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4888    fn refuse<T>(message: &str) -> Result<T, async_graphql::Error> {
4889        Err(coded_error("parse-failed", message))
4890    }
4891
4892    fn position(
4893        value: &serde_json::Value,
4894    ) -> Result<&Vec<serde_json::Value>, async_graphql::Error> {
4895        match value.as_array() {
4896            Some(items) if items.len() >= 2 && items.iter().all(|i| i.is_number()) => Ok(items),
4897            _ => refuse("A Position needs at least 2 elements"),
4898        }
4899    }
4900
4901    fn positions(
4902        value: Option<&serde_json::Value>,
4903        least: usize,
4904        what: &str,
4905    ) -> Result<Vec<Vec<serde_json::Value>>, async_graphql::Error> {
4906        let items = match value.and_then(|v| v.as_array()) {
4907            Some(items) => items,
4908            None => return refuse(&format!("A {} needs at least {} Positions", what, least)),
4909        };
4910        if items.len() < least {
4911            return refuse(&format!("A {} needs at least {} Positions", what, least));
4912        }
4913        items.iter().map(|item| position(item).cloned()).collect()
4914    }
4915
4916    fn ring(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4917        let points = positions(Some(value), 4, "LinearRing")?;
4918        match (points.first(), points.last()) {
4919            (Some(first), Some(last)) if first == last => Ok(()),
4920            _ => refuse("the first and last locations have to be equal for a LinearRing"),
4921        }
4922    }
4923
4924    fn polygon(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4925        let Some(rings) = value.as_array() else {
4926            return refuse("A LinearRing needs at least 4 Positions");
4927        };
4928        rings.iter().try_for_each(ring)
4929    }
4930
4931    let serde_json::Value::Object(map) = value else {
4932        return Ok(());
4933    };
4934    let Some(kind) = map.get("type").and_then(|v| v.as_str()) else {
4935        return Ok(());
4936    };
4937    let coordinates = map.get("coordinates");
4938    match kind {
4939        "Point" => {
4940            position(coordinates.unwrap_or(&serde_json::Value::Null))?;
4941        }
4942        "MultiPoint" => {
4943            positions(coordinates, 0, "MultiPoint")?;
4944        }
4945        "LineString" => {
4946            positions(coordinates, 2, "LineString")?;
4947        }
4948        "MultiLineString" => {
4949            for line in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4950                positions(Some(line), 2, "LineString")?;
4951            }
4952        }
4953        "Polygon" => polygon(coordinates.unwrap_or(&serde_json::Value::Null))?,
4954        "MultiPolygon" => {
4955            for one in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4956                polygon(one)?;
4957            }
4958        }
4959        "GeometryCollection" => {
4960            for geometry in map
4961                .get("geometries")
4962                .and_then(|v| v.as_array())
4963                .into_iter()
4964                .flatten()
4965            {
4966                check_geojson(geometry)?;
4967            }
4968        }
4969        other => return refuse(&format!("unexpected geometry type: {}", other)),
4970    }
4971    Ok(())
4972}
4973
4974/// The call that produces a computed field's value.
4975///
4976/// Two spellings of the same row, because the two calls need different ones.
4977/// A positional call takes `alias.*`, which is what a qualified table can be
4978/// written as; a named call takes the row as one value, which only a bare
4979/// alias is. Named notation is needed exactly where a session argument is,
4980/// since the row is then not the only parameter and may not be the first.
4981fn computed_call(
4982    definition: &postrust_core::schema_cache::ComputedColumn,
4983    row: &str,
4984    row_by_name: &str,
4985) -> String {
4986    let function = format!(
4987        "{}.{}",
4988        postrust_sql::escape_ident(&definition.function.schema),
4989        postrust_sql::escape_ident(&definition.function.name)
4990    );
4991    match (&definition.session_argument, &definition.row_argument) {
4992        (Some(session), Some(row_argument)) => format!(
4993            "{}({} => {}, {} => {})",
4994            function,
4995            postrust_sql::escape_ident(row_argument),
4996            row_by_name,
4997            postrust_sql::escape_ident(session),
4998            SESSION_ARGUMENT
4999        ),
5000        _ => format!("{}({})", function, row),
5001    }
5002}
5003
5004/// What a `hasura_session` argument is given.
5005///
5006/// The session document, read from the setting `begin_with_session` writes.
5007/// `current_setting`'s second argument makes a missing setting null rather than
5008/// an error, which is the case of a request with no session at all.
5009const SESSION_ARGUMENT: &str = "coalesce(current_setting('hasura.session', true), '{}')::json";
5010
5011/// Everything an update may be told to do, in the order the schema declares
5012/// them. Read by both spellings: one update, and one of many.
5013const UPDATE_OPERATORS: [&str; 7] = [
5014    "_set",
5015    "_inc",
5016    "_append",
5017    "_prepend",
5018    "_delete_key",
5019    "_delete_elem",
5020    "_delete_at_path",
5021];
5022
5023/// What a read table is called inside its own query.
5024///
5025/// See [`WRITTEN_ROW`]: the row a query passes to a computed field has to be
5026/// named, and a table's qualified name is not a name a row can be written
5027/// under.
5028const READ_ROW: &str = "pgrst_src";
5029
5030/// What a written table is called inside its own statement.
5031///
5032/// See [`WhereScope::under_alias`]: the row a statement returns has to be
5033/// named, and a table's own name is read as a column first.
5034const WRITTEN_ROW: &str = "pgrst_row";
5035
5036/// The cast a written value needs to reach a column of this type.
5037///
5038/// A bound parameter arrives as text. PostgreSQL will coerce it to a numeric
5039/// or a date on assignment, but not to `jsonb`, an array, or a user-defined
5040/// type -- `column "details" is of type jsonb but expression is of type text`
5041/// is what an uncast insert answers. Naming the column's own type covers all
5042/// of them and changes nothing where the coercion would have worked anyway.
5043fn write_cast(column_types: &HashMap<String, String>, column: &str) -> String {
5044    match column_types.get(column) {
5045        Some(pg_type) => format!("::{}", pg_type),
5046        None => String::new(),
5047    }
5048}
5049
5050/// The value one column is written with, as it goes over the wire.
5051///
5052/// A `json` column holds a JSON value, and `"[]"` is one -- a string whose
5053/// characters happen to be brackets. Binding the bare text and casting stored
5054/// an empty array instead, so a client that wrote a string read back a list.
5055/// The document a column is given is the document it keeps.
5056fn write_operand(
5057    column_types: &HashMap<String, String>,
5058    column: &str,
5059    value: &serde_json::Value,
5060) -> serde_json::Value {
5061    match column_types.get(column).map(String::as_str) {
5062        Some("json") | Some("jsonb") => match value {
5063            serde_json::Value::String(_)
5064            | serde_json::Value::Number(_)
5065            | serde_json::Value::Bool(_) => serde_json::Value::String(value.to_string()),
5066            other => other.clone(),
5067        },
5068        // An array column takes an array literal. `["a","b"]` is JSON, and
5069        // PostgreSQL reads a text parameter destined for a `text[]` as
5070        // `{a,b}` -- `malformed array literal` is what it says about the
5071        // other spelling.
5072        Some(pg_type) if is_array_type(pg_type) => match value {
5073            serde_json::Value::Array(_) => serde_json::Value::String(array_literal(value)),
5074            other => other.clone(),
5075        },
5076        _ => value.clone(),
5077    }
5078}
5079
5080/// Whether a PostgreSQL type name is an array.
5081///
5082/// Both spellings appear in the catalogue depending on how it was asked:
5083/// `text[]` from `format_type`, `_text` from `typname`.
5084fn is_array_type(pg_type: &str) -> bool {
5085    pg_type.ends_with("[]") || pg_type.starts_with('_')
5086}
5087
5088/// A JSON array as PostgreSQL writes one.
5089///
5090/// `["a","b"]` becomes `{"a","b"}`. Every element is quoted, so a value
5091/// containing a comma, a brace or a backslash survives; a null is the unquoted
5092/// `NULL`, which is the only way to write one.
5093fn array_literal(value: &serde_json::Value) -> String {
5094    fn element(value: &serde_json::Value) -> String {
5095        match value {
5096            serde_json::Value::Null => "NULL".to_string(),
5097            serde_json::Value::Array(_) => array_literal(value),
5098            serde_json::Value::String(text) => {
5099                format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
5100            }
5101            other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
5102        }
5103    }
5104    let items = match value {
5105        serde_json::Value::Array(items) => items,
5106        other => return element(other),
5107    };
5108    format!(
5109        "{{{}}}",
5110        items.iter().map(element).collect::<Vec<_>>().join(",")
5111    )
5112}
5113
5114/// The expression that writes one bound value into one column.
5115///
5116/// A cast is enough for almost everything. A shape is the exception: a client
5117/// sends GeoJSON, which is what Hasura accepts, and `'{"type":"Point",…}'` is
5118/// not something PostgreSQL will cast to a geometry -- it has a function for
5119/// reading that, and the function is the only way in.
5120///
5121/// Only when the value actually is an object. A string in a geometry column is
5122/// WKT or WKB hex, which the cast does read, and passing that to
5123/// `ST_GeomFromGeoJSON` would refuse a perfectly good value.
5124fn write_expression(
5125    column_types: &HashMap<String, String>,
5126    column: &str,
5127    value: &serde_json::Value,
5128    placeholder: &str,
5129) -> String {
5130    let pg_type = column_types.get(column).map(String::as_str);
5131    if value.is_object() && matches!(pg_type, Some("geometry") | Some("geography")) {
5132        return match pg_type {
5133            Some("geography") => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
5134            _ => format!("ST_GeomFromGeoJSON({})", placeholder),
5135        };
5136    }
5137    // An amount written as a number is reached through one: PostgreSQL has no
5138    // cast from `double precision` to `money`, which is what a bound JSON
5139    // number arrives as, and `numeric` is the type it does take one from. One
5140    // written as text -- `"$12,344.57"` -- is read by `money` directly, and
5141    // going through `numeric` would refuse the currency symbol.
5142    if pg_type == Some("money") && value.is_number() {
5143        return format!("{}::numeric::money", placeholder);
5144    }
5145    format!("{}{}", placeholder, write_cast(column_types, column))
5146}
5147
5148/// Every column of a table with the type it is declared as.
5149async fn column_types_of(
5150    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
5151    schema_name: &str,
5152    table_name: &str,
5153) -> HashMap<String, String> {
5154    let Ok(guard) = schema_cache.get().await else {
5155        return HashMap::new();
5156    };
5157    let Some(cache) = guard.as_ref() else {
5158        return HashMap::new();
5159    };
5160    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5161    match cache.get_table(&qi) {
5162        Some(table) => table
5163            .columns
5164            .values()
5165            .map(|c| (c.name.clone(), c.nominal_type.clone()))
5166            .collect(),
5167        None => HashMap::new(),
5168    }
5169}
5170
5171/// Insert one row, and any rows nested inside it.
5172///
5173/// Hasura writes a parent and its children in one mutation:
5174///
5175/// ```graphql
5176/// insert_article(objects: [{title: "x", author: {data: {name: "y"}}}])
5177/// ```
5178///
5179/// Which row goes first follows from which side holds the key. A relationship
5180/// to one row is reached through a column of *this* table, so the related row
5181/// is inserted first and its key is what this row's column is set to. A
5182/// relationship to many rows is reached through a column of *theirs*, so this
5183/// row goes first and each child's column is set from it.
5184///
5185/// Everything runs in the transaction the caller opened, so a child that
5186/// cannot be written takes the parent with it -- which is what writing them in
5187/// one mutation means.
5188fn insert_row<'life>(
5189    conn: &'life mut sqlx::PgConnection,
5190    schema_name: &'life str,
5191    table_name: &'life str,
5192    object: serde_json::Map<String, serde_json::Value>,
5193    context: &'life InsertContext<'life>,
5194    written_count: &'life mut usize,
5195) -> std::pin::Pin<
5196    Box<
5197        dyn std::future::Future<Output = Result<serde_json::Value, async_graphql::Error>>
5198            + Send
5199            + 'life,
5200    >,
5201> {
5202    Box::pin(async move {
5203        use sqlx::Row;
5204
5205        let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5206        let table = context.cache.get_table(&qi).ok_or_else(|| {
5207            async_graphql::Error::new(format!("unknown table \"{}\"", table_name))
5208        })?;
5209        let column_types: HashMap<String, String> = table
5210            .columns
5211            .values()
5212            .map(|c| (c.name.clone(), c.nominal_type.clone()))
5213            .collect();
5214
5215        // A key that is a relationship rather than a column is a nested write.
5216        let type_name = context
5217            .type_names
5218            .get(&(schema_name.to_string(), table_name.to_string()));
5219        let relationships: &[RelationshipField] = type_name
5220            .and_then(|name| context.relationships.get(name))
5221            .map(|r| r.as_slice())
5222            .unwrap_or(&[]);
5223
5224        let mut columns = serde_json::Map::new();
5225        type Nested<'r> = (
5226            &'r RelationshipField,
5227            serde_json::Value,
5228            Option<serde_json::Value>,
5229        );
5230        let mut to_one: Vec<Nested> = Vec::new();
5231        let mut to_many: Vec<Nested> = Vec::new();
5232
5233        for (key, value) in object {
5234            match relationships.iter().find(|r| r.name == key) {
5235                None => {
5236                    // A written value is keyed by the field it was sent under;
5237                    // the statement is written in the table's own columns.
5238                    columns.insert(
5239                        table_column_for(context.names, table, &key).to_string(),
5240                        value,
5241                    );
5242                }
5243                Some(_) if value.is_null() => {
5244                    // `author: null` is a row with no author, not a row whose
5245                    // author is a null object. A client writing one insert for
5246                    // both cases sends the relationship either way.
5247                    continue;
5248                }
5249                Some(relationship) => {
5250                    // `{data: {...}}` for one row, `{data: [{...}]}` for many,
5251                    // and an `on_conflict` beside either -- a nested row is
5252                    // upserted the same way a top-level one is.
5253                    let (data, conflict) = match &value {
5254                        serde_json::Value::Object(map) => (
5255                            map.get("data").cloned().unwrap_or(value.clone()),
5256                            map.get("on_conflict").cloned().filter(|v| !v.is_null()),
5257                        ),
5258                        other => (other.clone(), None),
5259                    };
5260                    // Which row goes first follows from which side holds the
5261                    // key, not from how many rows there are. A one-to-one
5262                    // whose child key *is* the parent's -- `author_detail.id`
5263                    // referencing `author.id` -- is one row either way, and
5264                    // the parent still has to be written before there is a key
5265                    // to give it.
5266                    if child_holds_the_key(&relationship.relationship) {
5267                        to_many.push((relationship, data, conflict));
5268                    } else {
5269                        to_one.push((relationship, data, conflict));
5270                    }
5271                }
5272            }
5273        }
5274
5275        // A shape is checked before it is written, since PostGIS would build
5276        // one from a document that is not a shape.
5277        for (column, value) in &columns {
5278            if matches!(
5279                column_types.get(column).map(String::as_str),
5280                Some("geometry") | Some("geography")
5281            ) {
5282                // Under the name the client wrote it under, which is the
5283                // exposed one rather than the column's where they differ.
5284                check_geojson(value).map_err(|error| {
5285                    let field = context
5286                        .names
5287                        .column(schema_name, table_name, column)
5288                        .unwrap_or(column);
5289                    at_path(error, &format!("{}.{}", context.row, field))
5290                })?;
5291            }
5292        }
5293
5294        // The rows this one points at, first: this row's own column carries
5295        // their key.
5296        for (relationship, data, conflict) in to_one {
5297            let plan =
5298                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5299                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5300            let serde_json::Value::Object(child) = data else {
5301                return Err(async_graphql::Error::new(format!(
5302                    "\"{}\" takes an object to insert",
5303                    relationship.name
5304                )));
5305            };
5306            // Writing the related row is what fills this row's key column in,
5307            // so a value already written there is a second answer to the same
5308            // question and one of them would be silently discarded.
5309            for (local, _) in &plan.columns {
5310                if columns.get(local).is_some_and(|v| !v.is_null()) {
5311                    return Err(at_path(
5312                        validation_error(format!(
5313                            "cannot insert object relationship \"{}\" as \"{}\" column \
5314                             values are already determined",
5315                            relationship.name, local
5316                        )),
5317                        &format!("{}.{}", context.row, relationship.name),
5318                    ));
5319                }
5320            }
5321            // A to-one row is written under the relationship's own name:
5322            // `...objects[0].author.data`, and its `on_conflict` beside it.
5323            let under = format!("{}.{}", context.row, relationship.name);
5324            let nested = InsertContext {
5325                on_conflict: conflict,
5326                cache: context.cache,
5327                relationships: context.relationships,
5328                type_names: context.type_names,
5329                names: context.names,
5330                caller: context.caller,
5331                row: format!("{}.data", under),
5332                objects: format!("{}.data", under),
5333                conflict: format!("{}.on_conflict", under),
5334            };
5335            let written = insert_row(
5336                conn,
5337                &plan.foreign_schema,
5338                &plan.foreign_table,
5339                child,
5340                &nested,
5341                written_count,
5342            )
5343            .await?;
5344            for (local, foreign) in &plan.columns {
5345                if let Some(value) = written.get(foreign) {
5346                    columns.insert(local.clone(), value.clone());
5347                }
5348            }
5349        }
5350
5351        // What the server fills in regardless of what was sent. Applied after
5352        // the nested rows have contributed their keys and before the columns
5353        // are counted, because a preset is a column this statement writes --
5354        // and applied *over* the request, since a preset the client could
5355        // override is not a preset.
5356        for (column, value) in crate::role::presets(
5357            &context.caller,
5358            context.names,
5359            schema_name,
5360            table_name,
5361            crate::role::Verb::Insert,
5362        )
5363        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?
5364        {
5365            columns.insert(column, value);
5366        }
5367
5368        // `ON CONFLICT` says which uniqueness is being resolved against and
5369        // what to do about it. An empty `update_columns` is `DO NOTHING`,
5370        // which is how a client says "leave the row that is already there".
5371        // The columns this statement writes, settled before the conflict
5372        // clause so a predicate on it can number its parameters after theirs.
5373        let column_names: Vec<&str> = columns.keys().map(|k| k.as_str()).collect();
5374        let mut conflict_values: Vec<serde_json::Value> = Vec::new();
5375        let conflict_sql = match &context.on_conflict {
5376            Some(serde_json::Value::Object(spec)) => {
5377                let constraint = spec.get("constraint").and_then(|v| v.as_str());
5378                let Some(constraint) = constraint else {
5379                    return Err(async_graphql::Error::new(
5380                        "on_conflict needs a constraint to resolve against",
5381                    ));
5382                };
5383                if !table
5384                    .unique_constraints
5385                    .iter()
5386                    .any(|(name, _)| name == constraint)
5387                {
5388                    return Err(async_graphql::Error::new(format!(
5389                        "\"{}\" is not a unique constraint of \"{}\"",
5390                        constraint, table_name
5391                    )));
5392                }
5393                let updates: Vec<&str> = spec
5394                    .get("update_columns")
5395                    .and_then(|v| v.as_array())
5396                    .map(|items| items.iter().filter_map(|i| i.as_str()).collect())
5397                    .unwrap_or_default();
5398                // The member that stands for the columns this role has none
5399                // of. It is in the enum so that `on_conflict` exists at all;
5400                // naming it is the one thing it cannot be used for.
5401                if updates.contains(&PLACEHOLDER_COLUMN) {
5402                    return Err(at_path(
5403                        coded_error("validation-failed", "erroneous column name"),
5404                        &context.conflict,
5405                    ));
5406                }
5407
5408                if updates.is_empty() {
5409                    format!(
5410                        " ON CONFLICT ON CONSTRAINT {} DO NOTHING",
5411                        postrust_sql::escape_ident(constraint)
5412                    )
5413                } else {
5414                    // An `ON CONFLICT DO UPDATE` is an update, so the update
5415                    // permission's presets are written here too -- and a
5416                    // column a preset writes does not also take its value from
5417                    // `EXCLUDED`, which is what "a preset overrides the
5418                    // request" means on this side.
5419                    let preset = crate::role::presets(
5420                        &context.caller,
5421                        context.names,
5422                        schema_name,
5423                        table_name,
5424                        crate::role::Verb::Update,
5425                    )
5426                    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5427
5428                    // Numbered after the row's own values and before the
5429                    // predicate below, because `SET` is written before `WHERE`
5430                    // and the parameters are bound in the order they appear.
5431                    let mut param_idx = column_names.len() + 1;
5432                    let mut assignments: Vec<String> =
5433                        Vec::with_capacity(preset.len() + updates.len());
5434                    for (column, value) in &preset {
5435                        assignments.push(format!(
5436                            "{} = {}",
5437                            postrust_sql::escape_ident(column),
5438                            write_expression(
5439                                &column_types,
5440                                column,
5441                                value,
5442                                &format!("${}", param_idx)
5443                            )
5444                        ));
5445                        conflict_values.push(write_operand(&column_types, column, value));
5446                        param_idx += 1;
5447                    }
5448                    for field in &updates {
5449                        let column = table_column_for(context.names, table, field);
5450                        if preset.iter().any(|(written, _)| written == column) {
5451                            continue;
5452                        }
5453                        assignments.push(format!(
5454                            "{} = EXCLUDED.{}",
5455                            postrust_sql::escape_ident(column),
5456                            postrust_sql::escape_ident(column)
5457                        ));
5458                    }
5459                    // `where` on an upsert decides whether the row that is
5460                    // already there is overwritten -- "only if what I am
5461                    // writing is newer". It reads the existing row, which is
5462                    // what the statement's alias names.
5463                    //
5464                    // The update permission's own filter is ANDed into it: an
5465                    // upsert that overwrites a row is an update, and a role
5466                    // that may not update a row may not reach it by inserting
5467                    // over it. Compiling that filter is also where a session
5468                    // variable it names and the caller does not carry is
5469                    // noticed -- which is the answer Hasura gives before it
5470                    // writes anything.
5471                    let granted = permission_filter(
5472                        &context.caller,
5473                        context.names,
5474                        schema_name,
5475                        table_name,
5476                        crate::role::Verb::Update,
5477                    )?;
5478                    let requested = spec.get("where").filter(|f| !f.is_null()).cloned();
5479                    let condition = match and_predicates(requested, granted) {
5480                        Some(filter) => {
5481                            let filter = &filter;
5482                            let type_name = context
5483                                .type_names
5484                                .get(&(schema_name.to_string(), table_name.to_string()))
5485                                .map(String::as_str)
5486                                .unwrap_or(table_name);
5487                            let scope = WhereScope::table(
5488                                schema_name,
5489                                table_name,
5490                                type_name,
5491                                context.names,
5492                            )
5493                            .under_alias(WRITTEN_ROW)
5494                            .with_resolution(context.cache, context.relationships);
5495                            let mut alias_counter = 0usize;
5496                            build_condition(
5497                                filter,
5498                                &scope,
5499                                &mut param_idx,
5500                                &mut conflict_values,
5501                                &mut alias_counter,
5502                            )?
5503                            .map(|sql| format!(" WHERE {}", sql))
5504                            .unwrap_or_default()
5505                        }
5506                        None => String::new(),
5507                    };
5508                    format!(
5509                        " ON CONFLICT ON CONSTRAINT {} DO UPDATE SET {}{}",
5510                        postrust_sql::escape_ident(constraint),
5511                        assignments.join(", "),
5512                        condition
5513                    )
5514                }
5515            }
5516            _ => String::new(),
5517        };
5518
5519        // The row this statement writes, as `RETURNING` names it. Not the
5520        // table's own name: qualifying it reads as a column of a table called
5521        // `public`, which is what `missing FROM-clause entry for table
5522        // "public"` means, and leaving it bare reads as a column of the table
5523        // where one shares the name.
5524        let qualified_table = postrust_sql::escape_ident(WRITTEN_ROW);
5525
5526        // What the written row has to satisfy for the write to stand,
5527        // evaluated in `RETURNING` against the row as written. Its parameters
5528        // are numbered after the values and the conflict clause, since
5529        // `RETURNING` is last.
5530        let check_scope = WhereScope::for_alias(
5531            schema_name,
5532            table_name,
5533            WRITTEN_ROW,
5534            context
5535                .type_names
5536                .get(&(schema_name.to_string(), table_name.to_string()))
5537                .map(String::as_str)
5538                .unwrap_or(table_name),
5539            context.cache,
5540            context.relationships,
5541            context.names,
5542        );
5543        let (check_sql, check_values) = {
5544            let check = crate::role::write_check(
5545                &context.caller,
5546                context.names,
5547                schema_name,
5548                table_name,
5549                crate::role::Verb::Insert,
5550            )
5551            .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5552            match check {
5553                None => (String::new(), Vec::new()),
5554                Some(check) => {
5555                    let mut values = Vec::new();
5556                    let mut param_idx = column_names.len() + conflict_values.len() + 1;
5557                    let mut alias_counter = 0usize;
5558                    match build_condition(
5559                        &check,
5560                        &check_scope,
5561                        &mut param_idx,
5562                        &mut values,
5563                        &mut alias_counter,
5564                    )? {
5565                        None => (String::new(), Vec::new()),
5566                        Some(sql) => (
5567                            format!(
5568                                ", ({}) AS {}",
5569                                sql,
5570                                postrust_sql::escape_ident(CHECK_COLUMN)
5571                            ),
5572                            values,
5573                        ),
5574                    }
5575                }
5576            }
5577        };
5578
5579        let names = column_names;
5580        let written = if names.is_empty() {
5581            // Every column defaulted. `DEFAULT VALUES` is how SQL says that;
5582            // an empty column list is a syntax error.
5583            let sql = format!(
5584                "INSERT INTO {}.{} AS {} DEFAULT VALUES{} RETURNING {}{}",
5585                postrust_sql::escape_ident(schema_name),
5586                postrust_sql::escape_ident(table_name),
5587                qualified_table,
5588                conflict_sql,
5589                row_json(&qualified_table, &column_types),
5590                check_sql
5591            );
5592            let mut query = sqlx::query(&sql);
5593            for value in &conflict_values {
5594                query = bind_json_value(query, value);
5595            }
5596            for value in &check_values {
5597                query = bind_json_value(query, value);
5598            }
5599            query
5600                .fetch_optional(&mut *conn)
5601                .await
5602                // A constraint the write violated is reported against the rows
5603                // the statement was given, which is where Hasura reports it.
5604                .map_err(|error| at_path(database_error(error), &context.objects))?
5605        } else {
5606            let placeholders: Vec<String> = names
5607                .iter()
5608                .enumerate()
5609                .map(|(i, column)| {
5610                    let placeholder = format!("${}", i + 1);
5611                    match columns.get(*column) {
5612                        Some(value) => write_expression(&column_types, column, value, &placeholder),
5613                        None => placeholder,
5614                    }
5615                })
5616                .collect();
5617            let sql = format!(
5618                "INSERT INTO {}.{} AS {} ({}) VALUES ({}){} RETURNING {}{}",
5619                postrust_sql::escape_ident(schema_name),
5620                postrust_sql::escape_ident(table_name),
5621                qualified_table,
5622                names
5623                    .iter()
5624                    .map(|c| postrust_sql::escape_ident(c))
5625                    .collect::<Vec<_>>()
5626                    .join(", "),
5627                placeholders.join(", "),
5628                conflict_sql,
5629                row_json(&qualified_table, &column_types),
5630                check_sql
5631            );
5632            trace!("Executing INSERT SQL: {}", sql);
5633            let mut query = sqlx::query(&sql);
5634            for column in &names {
5635                if let Some(value) = columns.get(*column) {
5636                    query = bind_json_value(query, &write_operand(&column_types, column, value));
5637                }
5638            }
5639            for value in &conflict_values {
5640                query = bind_json_value(query, value);
5641            }
5642            for value in &check_values {
5643                query = bind_json_value(query, value);
5644            }
5645            query
5646                .fetch_optional(&mut *conn)
5647                .await
5648                // A constraint the write violated is reported against the rows
5649                // the statement was given, which is where Hasura reports it.
5650                .map_err(|error| at_path(database_error(error), &context.objects))?
5651        };
5652
5653        // `DO NOTHING` writes nothing and returns nothing, which is the answer
5654        // rather than an error: the row that was already there stays.
5655        let Some(written) = written else {
5656            return Ok(serde_json::Value::Null);
5657        };
5658        refuse_unchecked_rows(std::slice::from_ref(&written), !check_sql.is_empty())
5659            .map_err(|error| at_path(error, &context.objects))?;
5660        let row: serde_json::Value = written.try_get(0).unwrap_or(serde_json::Value::Null);
5661        *written_count += 1;
5662
5663        // Then the rows that point at this one, which need its key.
5664        for (relationship, data, conflict) in to_many {
5665            let plan =
5666                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5667                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5668            let children = match data {
5669                serde_json::Value::Array(items) => items,
5670                serde_json::Value::Object(map) => vec![serde_json::Value::Object(map)],
5671                serde_json::Value::Null => Vec::new(),
5672                _ => {
5673                    return Err(async_graphql::Error::new(format!(
5674                        "\"{}\" takes objects to insert",
5675                        relationship.name
5676                    )))
5677                }
5678            };
5679            for (child_index, child) in children.into_iter().enumerate() {
5680                let serde_json::Value::Object(mut child) = child else {
5681                    continue;
5682                };
5683                for (local, foreign) in &plan.columns {
5684                    // The parent's key is what makes the child a child, so a
5685                    // child that writes it too is asking for a different
5686                    // parent than the one it is nested inside.
5687                    if child.get(foreign).is_some_and(|v| !v.is_null()) {
5688                        return Err(at_path(
5689                            validation_error(format!(
5690                                "cannot insert \"{}\" columns as their values are already \
5691                                 being determined by parent insert",
5692                                foreign
5693                            )),
5694                            &format!(
5695                                "{}.{}{}",
5696                                context.row,
5697                                relationship.name,
5698                                match relationship.is_list {
5699                                    true => format!(".data[{}]", child_index),
5700                                    false => ".data".to_string(),
5701                                }
5702                            ),
5703                        ));
5704                    }
5705                    if let Some(value) = row.get(local) {
5706                        child.insert(foreign.clone(), value.clone());
5707                    }
5708                }
5709                // `...objects[0].articles.data[0]`, which is the path
5710                // Hasura answers for a child that writes its parent's key.
5711                // One row written after its parent -- a mapped object
5712                // relationship with `after_parent` -- has no index: there is
5713                // no list for it to be an item of.
5714                let at = match relationship.is_list {
5715                    true => format!(".data[{}]", child_index),
5716                    false => ".data".to_string(),
5717                };
5718                let under = format!("{}.{}", context.row, relationship.name);
5719                let nested = InsertContext {
5720                    on_conflict: conflict.clone(),
5721                    cache: context.cache,
5722                    relationships: context.relationships,
5723                    type_names: context.type_names,
5724                    names: context.names,
5725                    caller: context.caller,
5726                    row: format!("{}{}", under, at),
5727                    objects: format!("{}.data", under),
5728                    conflict: format!("{}.on_conflict", under),
5729                };
5730                insert_row(
5731                    conn,
5732                    &plan.foreign_schema,
5733                    &plan.foreign_table,
5734                    child,
5735                    &nested,
5736                    written_count,
5737                )
5738                .await?;
5739            }
5740        }
5741
5742        Ok(row)
5743    })
5744}
5745
5746/// What a nested insert needs to follow a relationship.
5747///
5748/// Cloned per row and per nested row, which costs three strings: the paths are
5749/// the only part that differs between them, and the rest is references and a
5750/// `Caller`.
5751#[derive(Clone)]
5752struct InsertContext<'a> {
5753    /// What to do when this row conflicts. A nested row carries its own, from
5754    /// the `on_conflict` beside its `data`.
5755    on_conflict: Option<serde_json::Value>,
5756    cache: &'a SchemaCache,
5757    relationships: &'a HashMap<String, Vec<RelationshipField>>,
5758    /// (schema, table) -> the GraphQL type name, which keys the relationship
5759    /// map. A table's type is not always its name: a second schema prefixes
5760    /// it, and a name may have been given.
5761    type_names: &'a HashMap<(String, String), String>,
5762    /// The names columns are exposed under, so a written value keyed by a
5763    /// field reaches the column it names.
5764    names: &'a crate::names::NameOverrides,
5765    /// Who is writing, so that a permission's presets and its check reach
5766    /// every row this insert writes -- including the nested ones, which are
5767    /// rows of other tables under other rules.
5768    caller: crate::role::Caller<'a>,
5769    /// Where this row is in the request, as Hasura writes such a place:
5770    /// `$.selectionSet.insert_author.args.objects[0]`, and one level down
5771    /// `$.selectionSet.insert_author.args.objects[0].articles.data[0]`. A
5772    /// column of it is that with the field's name appended.
5773    row: String,
5774    /// Where the rows this statement writes were named. What a refused `check`
5775    /// is reported against -- the argument, not the row inside it, which is
5776    /// what Hasura answers.
5777    objects: String,
5778    /// Where this statement's `on_conflict` was named.
5779    conflict: String,
5780}
5781
5782/// Re-read written rows so that a mutation's `returning` can carry
5783/// relationships and computed fields.
5784///
5785/// `RETURNING row_to_json(t.*)` gives the table's own columns and nothing
5786/// else, so a relationship asked for beside them had no value to resolve --
5787/// and a non-null list field with no value is an error, which meant a mutation
5788/// that had written its rows correctly answered as though it had failed. The
5789/// write is not in doubt; only the shape of the answer is.
5790///
5791/// So the rows are read again by their primary key, through the same
5792/// projection an ordinary query uses. One extra round trip, and only when the
5793/// selection actually asks for something `RETURNING` cannot give.
5794#[allow(clippy::too_many_arguments)]
5795async fn reread_returning(
5796    pool: &PgPool,
5797    gql_ctx: &GraphQLContext,
5798    schema_name: &str,
5799    table_name: &str,
5800    type_name: &str,
5801    rows: Vec<Value>,
5802    returning: async_graphql::SelectionField<'_>,
5803    relationships: &HashMap<String, Vec<RelationshipField>>,
5804    names: &crate::names::NameOverrides,
5805    max_rows: Option<i64>,
5806) -> Result<Vec<Value>, async_graphql::Error> {
5807    let guard = gql_ctx
5808        .schema_cache
5809        .get()
5810        .await
5811        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5812    let Some(cache) = guard.as_ref() else {
5813        return Ok(rows);
5814    };
5815    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5816    let Some(table) = cache.get_table(&qi) else {
5817        return Ok(rows);
5818    };
5819    if table.pk_cols.is_empty() {
5820        // Nothing to identify the rows by. The columns are still right.
5821        return Ok(rows);
5822    }
5823
5824    let mut param_idx = 1usize;
5825    let mut values: Vec<serde_json::Value> = Vec::new();
5826    let mut alias_counter = 0usize;
5827    let embeds = build_embed_expressions(
5828        &gql_ctx.caller(),
5829        cache,
5830        relationships,
5831        type_name,
5832        "src",
5833        returning.selection_set(),
5834        max_rows,
5835        &mut alias_counter,
5836        &mut param_idx,
5837        &mut values,
5838        names,
5839    )?;
5840    let computed = computed_projections(
5841        table,
5842        returning.selection_set(),
5843        "src",
5844        names,
5845        cache,
5846        &mut param_idx,
5847        &mut values,
5848    )?;
5849    // A rename is reason enough to read the rows again: `RETURNING` answers in
5850    // the table's own column names, which are not the names the client asked
5851    // under.
5852    let renames = names.renames_columns(&table.schema, &table.name);
5853    if embeds.is_empty() && computed.is_empty() && !renames {
5854        return Ok(rows);
5855    }
5856
5857    // One row per key written, in the order they were written.
5858    let mut conditions = Vec::with_capacity(rows.len());
5859    for row in &rows {
5860        let mut parts = Vec::with_capacity(table.pk_cols.len());
5861        for column in &table.pk_cols {
5862            let Value::Object(map) = row else {
5863                return Ok(rows);
5864            };
5865            let Some(value) = map.get(&async_graphql::Name::new(column)) else {
5866                return Ok(rows);
5867            };
5868            parts.push(format!(
5869                "{} = ${}",
5870                postrust_sql::escape_ident(column),
5871                param_idx
5872            ));
5873            values.push(value_to_json(value));
5874            param_idx += 1;
5875        }
5876        conditions.push(format!("({})", parts.join(" AND ")));
5877    }
5878    if conditions.is_empty() {
5879        return Ok(rows);
5880    }
5881
5882    let mut projection =
5883        rename_projection(table, "src", names).unwrap_or_else(|| "src.*".to_string());
5884    for expression in &computed {
5885        projection.push_str(", ");
5886        projection.push_str(expression);
5887    }
5888    for (name, expression) in &embeds {
5889        projection.push_str(", ");
5890        projection.push_str(expression);
5891        projection.push_str(" AS ");
5892        projection.push_str(&postrust_sql::escape_ident(name));
5893    }
5894
5895    let sql = format!(
5896        "SELECT {} FROM (SELECT {} FROM (SELECT * FROM {}.{} WHERE {}) AS src) AS pgrst_r",
5897        row_json("pgrst_r", &exposed_column_types(table, names)),
5898        projection,
5899        postrust_sql::escape_ident(schema_name),
5900        postrust_sql::escape_ident(table_name),
5901        conditions.join(" OR ")
5902    );
5903
5904    // The same transaction the write went into: the rows it is reading are the
5905    // rows that write produced, and nothing has committed yet.
5906    let mut guard = write_tx(gql_ctx, pool).await?;
5907    let conn = guard.as_mut().expect("write_tx opens one");
5908    let reread = execute_query_on(conn, &sql, &values).await?;
5909    drop(guard);
5910
5911    if reread.is_empty() {
5912        return Ok(rows);
5913    }
5914    Ok(reread.into_iter().map(json_to_value).collect())
5915}
5916
5917/// Execute an insert mutation.
5918async fn execute_insert(
5919    pool: &PgPool,
5920    gql_ctx: &GraphQLContext,
5921    schema_name: &str,
5922    table_name: &str,
5923    objects: serde_json::Value,
5924    context: &InsertContext<'_>,
5925    // Whether the client wrote one row rather than a list of them. Only the
5926    // error paths care: `insert_x_one(object: {...})` has no row to index, so
5927    // its rows are named `args.object` and not `args.object[0]`.
5928    single: bool,
5929) -> Result<(Vec<Value>, usize), async_graphql::Error> {
5930    trace!("Insert mutation for {}: {:?}", table_name, objects);
5931
5932    // Handle both array and single object
5933    let objects_array = match objects {
5934        serde_json::Value::Array(arr) => arr,
5935        serde_json::Value::Object(obj) => vec![serde_json::Value::Object(obj)],
5936        _ => {
5937            return Err(async_graphql::Error::new(
5938                "objects must be an array or object",
5939            ))
5940        }
5941    };
5942
5943    if objects_array.is_empty() {
5944        return Err(async_graphql::Error::new("objects cannot be empty"));
5945    }
5946
5947    let mut guard = write_tx(gql_ctx, pool).await?;
5948    let conn = guard.as_mut().expect("write_tx opens one");
5949
5950    let mut inserted: Vec<Value> = Vec::new();
5951    let mut written = 0usize;
5952
5953    for (index, object) in objects_array.into_iter().enumerate() {
5954        let serde_json::Value::Object(map) = object else {
5955            return Err(async_graphql::Error::new(
5956                "each object to insert is an object",
5957            ));
5958        };
5959        // This row's own place in the request, which is what an error inside
5960        // it is reported against.
5961        let context = InsertContext {
5962            row: match single {
5963                true => context.objects.clone(),
5964                false => format!("{}[{}]", context.objects, index),
5965            },
5966            ..context.clone()
5967        };
5968        let row = insert_row(conn, schema_name, table_name, map, &context, &mut written).await?;
5969        // A row `DO NOTHING` left alone is not in `returning` and is not in
5970        // `affected_rows` either: nothing was written, and the row that was
5971        // already there is not this mutation's to report.
5972        if !row.is_null() {
5973            inserted.push(json_to_value(row));
5974        }
5975    }
5976
5977    Ok((inserted, written))
5978}
5979
5980/// Shape a mutation's result for the field that asked for it.
5981///
5982/// A by-key mutation answers with the row, or with null where the key matched
5983/// nothing. Every other mutation answers with the table's mutation response,
5984/// which carries `affected_rows` alongside the rows themselves -- a client may
5985/// ask for no rows back and still need the count, which is the usual case for
5986/// a delete.
5987///
5988/// The count is passed in rather than taken from the rows, because a nested
5989/// insert writes more rows than it returns: two articles each carrying a new
5990/// author is four rows written and two returned, and four is the answer.
5991fn mutation_result<'a>(rows: Vec<Value>, affected: usize, by_key: bool) -> Option<FieldValue<'a>> {
5992    if by_key {
5993        return rows.into_iter().next().map(FieldValue::value);
5994    }
5995    let mut response = async_graphql::indexmap::IndexMap::new();
5996    response.insert(
5997        async_graphql::Name::new("affected_rows"),
5998        Value::from(affected),
5999    );
6000    response.insert(async_graphql::Name::new("returning"), Value::List(rows));
6001    Some(FieldValue::value(Value::Object(response)))
6002}
6003
6004/// Bind a JSON value to a sqlx query.
6005fn bind_json_value<'q>(
6006    query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
6007    value: &serde_json::Value,
6008) -> sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments> {
6009    match value {
6010        serde_json::Value::Null => query.bind(None::<String>),
6011        serde_json::Value::Bool(b) => query.bind(*b),
6012        serde_json::Value::Number(n) => {
6013            if let Some(i) = n.as_i64() {
6014                query.bind(i)
6015            } else if let Some(f) = n.as_f64() {
6016                query.bind(f)
6017            } else {
6018                query.bind(n.to_string())
6019            }
6020        }
6021        serde_json::Value::String(s) => query.bind(s.clone()),
6022        _ => query.bind(value.to_string()),
6023    }
6024}
6025
6026/// Execute an update mutation.
6027#[allow(clippy::too_many_arguments)] // the predicate needs the schema it reads
6028async fn execute_update(
6029    pool: &PgPool,
6030    gql_ctx: &GraphQLContext,
6031    schema_name: &str,
6032    table_name: &str,
6033    type_name: &str,
6034    operators: Vec<(&'static str, serde_json::Value)>,
6035    column_types: HashMap<String, String>,
6036    where_clause: Option<serde_json::Value>,
6037    relationships: &HashMap<String, Vec<RelationshipField>>,
6038    names: &crate::names::NameOverrides,
6039) -> Result<Vec<Value>, async_graphql::Error> {
6040    use sqlx::Row;
6041
6042    trace!("Update mutation for {}: {:?}", table_name, operators);
6043
6044    // Build the SET clause. Each operator writes a column in terms of itself
6045    // except `_set`, which replaces it -- `_inc` adds, the jsonb operators
6046    // concatenate or remove, and a client may send several in one mutation as
6047    // long as they name different columns.
6048    let mut set_parts: Vec<String> = Vec::new();
6049    let mut set_values: Vec<serde_json::Value> = Vec::new();
6050    let mut param_idx = 1;
6051    let mut written: HashSet<String> = HashSet::new();
6052
6053    // What the permission writes whatever the request said. A preset overrides
6054    // rather than collides -- one a client could override is not a preset --
6055    // so these go in first and the operators below step around the columns
6056    // they name.
6057    //
6058    // This is also what makes an update with no `_set` at all a real update:
6059    // `update_resident(where: {...})` under a permission whose `set` names
6060    // `city` changes `city`, and answers with the rows it changed rather than
6061    // with nothing.
6062    let preset = crate::role::presets(
6063        &gql_ctx.caller(),
6064        names,
6065        schema_name,
6066        table_name,
6067        crate::role::Verb::Update,
6068    )
6069    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6070    let preset_columns: HashSet<&str> = preset.iter().map(|(name, _)| name.as_str()).collect();
6071    for (column, value) in &preset {
6072        let placeholder =
6073            write_expression(&column_types, column, value, &format!("${}", param_idx));
6074        set_parts.push(format!(
6075            "{} = {}",
6076            postrust_sql::escape_ident(column),
6077            placeholder
6078        ));
6079        set_values.push(write_operand(&column_types, column, value));
6080        param_idx += 1;
6081    }
6082
6083    for (operator, payload) in &operators {
6084        let serde_json::Value::Object(map) = payload else {
6085            return Err(async_graphql::Error::new(format!(
6086                "\"{}\" takes an object mapping columns to values",
6087                operator
6088            )));
6089        };
6090        for (field, value) in map {
6091            let column = &names
6092                .column_source(schema_name, table_name, field)
6093                .unwrap_or(field)
6094                .to_string();
6095            // A preset has this column. Not the duplicate-write error below:
6096            // the request is allowed to name it, and the permission wins.
6097            if preset_columns.contains(column.as_str()) {
6098                continue;
6099            }
6100            if !written.insert(column.clone()) {
6101                return Err(async_graphql::Error::new(format!(
6102                    "\"{}\" is written twice in one update; a column may be \
6103                     changed by one operator at a time",
6104                    column
6105                )));
6106            }
6107            let quoted = postrust_sql::escape_ident(column);
6108            // `_set` writes the column and `_inc` adds to it, so both take a
6109            // value of the column's type and are cast to it -- `money + $1`
6110            // is not an operator PostgreSQL has, and neither is `money +
6111            // double precision`, which is what a bound number arrives as. The
6112            // others already say what they need -- `::jsonb` for a
6113            // concatenation, `::text[]` for a path -- and casting twice would
6114            // be wrong for `_delete_elem`, whose operand is an integer rather
6115            // than a value of the column.
6116            let placeholder = match *operator {
6117                "_set" | "_inc" => {
6118                    write_expression(&column_types, column, value, &format!("${}", param_idx))
6119                }
6120                _ => format!("${}", param_idx),
6121            };
6122            // The assignment PostgreSQL needs, which for everything but `_set`
6123            // reads the column's current value.
6124            let assignment = match *operator {
6125                "_set" => format!("{} = {}", quoted, placeholder),
6126                "_inc" => format!("{} = {} + {}", quoted, quoted, placeholder),
6127                "_append" => format!("{} = {} || {}::jsonb", quoted, quoted, placeholder),
6128                "_prepend" => format!("{} = {}::jsonb || {}", quoted, placeholder, quoted),
6129                "_delete_key" => format!("{} = {} - {}::text", quoted, quoted, placeholder),
6130                "_delete_elem" => format!("{} = {} - {}::int", quoted, quoted, placeholder),
6131                // A path is a list of keys, and one parameter carrying
6132                // `["name","last"]` is a JSON array -- which PostgreSQL reads
6133                // as an array literal and refuses, `malformed array literal`.
6134                // The elements are bound one at a time into an `ARRAY[...]`,
6135                // the same way the key-existence comparisons take theirs.
6136                "_delete_at_path" => {
6137                    let Some(steps) = value.as_array() else {
6138                        return Err(async_graphql::Error::new(format!(
6139                            "\"_delete_at_path\" on \"{}\" takes a list of keys",
6140                            column
6141                        )));
6142                    };
6143                    let path = sql_array(steps, "text[]", &mut param_idx, &mut set_values);
6144                    set_parts.push(format!("{} = {} #- {}", quoted, quoted, path));
6145                    continue;
6146                }
6147                other => {
6148                    return Err(async_graphql::Error::new(format!(
6149                        "unsupported update operator \"{}\"",
6150                        other
6151                    )))
6152                }
6153            };
6154            if *operator == "_set"
6155                && matches!(
6156                    column_types.get(column).map(String::as_str),
6157                    Some("geometry") | Some("geography")
6158                )
6159            {
6160                check_geojson(value)?;
6161            }
6162            set_parts.push(assignment);
6163            set_values.push(match *operator {
6164                "_set" => write_operand(&column_types, column, value),
6165                _ => value.clone(),
6166            });
6167            param_idx += 1;
6168        }
6169    }
6170
6171    // An update that changes nothing changes no rows. Refusing it looks like
6172    // the stricter answer and is the wrong one: a client building `_set` from
6173    // a form the user submitted unchanged sends an empty object, and the
6174    // honest report of what happened is that nothing did.
6175    if set_parts.is_empty() {
6176        return Ok(Vec::new());
6177    }
6178
6179    // Build WHERE clause. It can follow a relationship, the same way a query's
6180    // can: `update_article(where: {author: {name: {_eq: "x"}}})` names the
6181    // rows by something they point at rather than by a column of their own.
6182    let guard = gql_ctx
6183        .schema_cache
6184        .get()
6185        .await
6186        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6187    let cache = guard
6188        .as_ref()
6189        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6190    let scope = WhereScope::table(schema_name, table_name, type_name, names)
6191        .under_alias(WRITTEN_ROW)
6192        .with_resolution(cache, relationships);
6193    let (where_sql, where_values) = build_where_clause(where_clause.as_ref(), param_idx, &scope)?;
6194
6195    // An absent or unrecognised `where` argument yields an empty clause, which
6196    // would update every row in the table. Refuse instead.
6197    if where_sql.is_empty() {
6198        return Err(async_graphql::Error::new(format!(
6199            "update on \"{}\" requires a `where` argument with at least one \
6200             recognised condition; refusing to update every row",
6201            table_name
6202        )));
6203    }
6204
6205    // What the changed row has to satisfy for the change to stand. Evaluated
6206    // in the `RETURNING` clause, against the row as written -- which is the
6207    // only place it can honestly be evaluated, and costs neither a re-read nor
6208    // a way to identify the rows afterwards.
6209    let (check_sql, check_values) = permission_check_sql(
6210        gql_ctx,
6211        names,
6212        schema_name,
6213        table_name,
6214        crate::role::Verb::Update,
6215        &scope,
6216        param_idx + where_values.len(),
6217    )?;
6218
6219    let sql = format!(
6220        "UPDATE {}.{} AS {} SET {} {} RETURNING {}{}",
6221        postrust_sql::escape_ident(schema_name),
6222        postrust_sql::escape_ident(table_name),
6223        postrust_sql::escape_ident(WRITTEN_ROW),
6224        set_parts.join(", "),
6225        where_sql,
6226        row_json(&postrust_sql::escape_ident(WRITTEN_ROW), &column_types),
6227        check_sql
6228    );
6229
6230    trace!("Executing UPDATE SQL: {}", sql);
6231
6232    // Build query with parameters
6233    let mut query = sqlx::query(&sql);
6234
6235    // Bind SET values
6236    for val in &set_values {
6237        query = bind_json_value(query, val);
6238    }
6239
6240    // Bind WHERE values
6241    for val in &where_values {
6242        query = bind_json_value(query, val);
6243    }
6244
6245    // And the check's, last, because `RETURNING` is.
6246    for val in &check_values {
6247        query = bind_json_value(query, val);
6248    }
6249
6250    let mut guard = write_tx(gql_ctx, pool).await?;
6251    let conn = guard.as_mut().expect("write_tx opens one");
6252    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6253
6254    refuse_unchecked_rows(&rows, !check_sql.is_empty())?;
6255
6256    let updated: Vec<Value> = rows
6257        .iter()
6258        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6259        .map(json_to_value)
6260        .collect();
6261
6262    Ok(updated)
6263}
6264
6265/// A permission's check, as a column of the write's `RETURNING`.
6266///
6267/// Empty where nothing has to be checked. Otherwise `, (<predicate>) AS ...`,
6268/// evaluated per written row against the row itself -- so a row the permission
6269/// refuses is known before the transaction is settled, and settling it is
6270/// already a rollback once an error is reported.
6271fn permission_check_sql(
6272    gql_ctx: &GraphQLContext,
6273    names: &crate::names::NameOverrides,
6274    schema_name: &str,
6275    table_name: &str,
6276    verb: crate::role::Verb,
6277    scope: &WhereScope<'_>,
6278    start_param_idx: usize,
6279) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6280    let check = crate::role::write_check(&gql_ctx.caller(), names, schema_name, table_name, verb)
6281        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6282    let Some(check) = check else {
6283        return Ok((String::new(), Vec::new()));
6284    };
6285
6286    let mut values = Vec::new();
6287    let mut param_idx = start_param_idx;
6288    let mut alias_counter = 0usize;
6289    let condition = build_condition(
6290        &check,
6291        scope,
6292        &mut param_idx,
6293        &mut values,
6294        &mut alias_counter,
6295    )?;
6296    Ok(match condition {
6297        // A check that compiles to nothing restricts nothing, which is what an
6298        // empty one says.
6299        None => (String::new(), Vec::new()),
6300        Some(sql) => (
6301            format!(
6302                ", ({}) AS {}",
6303                sql,
6304                postrust_sql::escape_ident(CHECK_COLUMN)
6305            ),
6306            values,
6307        ),
6308    })
6309}
6310
6311/// The column a permission's check is returned in.
6312const CHECK_COLUMN: &str = "pgrst_permission_check";
6313
6314/// Refuse the whole write if any row it produced fails the permission's check.
6315///
6316/// Every row, not the first: a mutation is one transaction and a single
6317/// refused row takes the rest with it, which is what makes a check a rule
6318/// about the write rather than about a row. A null counts as a failure -- an
6319/// unknown is not a satisfied condition.
6320fn refuse_unchecked_rows(
6321    rows: &[sqlx::postgres::PgRow],
6322    checked: bool,
6323) -> Result<(), async_graphql::Error> {
6324    if !checked {
6325        return Ok(());
6326    }
6327    use sqlx::Row;
6328    let all_passed = rows
6329        .iter()
6330        .all(|row| row.try_get::<bool, _>(CHECK_COLUMN).unwrap_or(false));
6331    match all_passed {
6332        true => Ok(()),
6333        false => Err(coded_error("permission-error", crate::role::CHECK_FAILED)),
6334    }
6335}
6336
6337/// Execute a delete mutation.
6338#[allow(clippy::too_many_arguments)] // the projection needs the schema it reads
6339async fn execute_delete(
6340    pool: &PgPool,
6341    gql_ctx: &GraphQLContext,
6342    schema_name: &str,
6343    table_name: &str,
6344    type_name: &str,
6345    where_clause: Option<serde_json::Value>,
6346    returning: Option<async_graphql::SelectionField<'_>>,
6347    relationships: &HashMap<String, Vec<RelationshipField>>,
6348    names: &crate::names::NameOverrides,
6349    max_rows: Option<i64>,
6350) -> Result<Vec<Value>, async_graphql::Error> {
6351    use sqlx::Row;
6352
6353    trace!("Delete mutation for {}", table_name);
6354
6355    let guard = gql_ctx
6356        .schema_cache
6357        .get()
6358        .await
6359        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6360    let cache = guard
6361        .as_ref()
6362        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6363    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
6364    let column_types = cache
6365        .get_table(&qi)
6366        .map(|table| exposed_column_types(table, names))
6367        .unwrap_or_default();
6368
6369    // Build WHERE clause. It can follow a relationship -- `delete_article(where:
6370    // {author: {id: {_eq: 2}}})` is the article-by-author delete anyone writes
6371    // -- so the scope is given the schema to resolve one with.
6372    let scope = WhereScope::table(schema_name, table_name, type_name, names)
6373        .under_alias(WRITTEN_ROW)
6374        .with_resolution(cache, relationships);
6375    let (where_sql, mut values) = build_where_clause(where_clause.as_ref(), 1, &scope)?;
6376
6377    // An absent or unrecognised `where` argument yields an empty clause, which
6378    // would delete every row in the table. Refuse instead.
6379    if where_sql.is_empty() {
6380        return Err(async_graphql::Error::new(format!(
6381            "delete on \"{}\" requires a `where` argument with at least one \
6382             recognised condition; refusing to delete every row",
6383            table_name
6384        )));
6385    }
6386
6387    // A relationship or computed field asked for in `returning` cannot be read
6388    // afterwards -- the rows are gone by then, which is why this used to answer
6389    // the plain columns and then fail on a non-null list. Inside one statement
6390    // the deleted rows are still a table: the delete goes in a CTE and the
6391    // projection reads from it, while the rows it points at are still there.
6392    let mut projection = String::new();
6393    let renamed = cache
6394        .get_table(&qi)
6395        .and_then(|table| rename_projection(table, "src", names));
6396    if let Some(returning) = returning {
6397        let mut param_idx = values.len() + 1;
6398        let mut alias_counter = 0usize;
6399        let embeds = build_embed_expressions(
6400            &gql_ctx.caller(),
6401            cache,
6402            relationships,
6403            type_name,
6404            "src",
6405            returning.selection_set(),
6406            max_rows,
6407            &mut alias_counter,
6408            &mut param_idx,
6409            &mut values,
6410            names,
6411        )?;
6412        let computed = match cache.get_table(&qi) {
6413            Some(table) => computed_projections(
6414                table,
6415                returning.selection_set(),
6416                "src",
6417                names,
6418                cache,
6419                &mut param_idx,
6420                &mut values,
6421            )?,
6422            None => Vec::new(),
6423        };
6424        for expression in &computed {
6425            projection.push_str(", ");
6426            projection.push_str(expression);
6427        }
6428        for (name, expression) in &embeds {
6429            projection.push_str(", ");
6430            projection.push_str(expression);
6431            projection.push_str(" AS ");
6432            projection.push_str(&postrust_sql::escape_ident(name));
6433        }
6434    }
6435
6436    let written = postrust_sql::escape_ident(WRITTEN_ROW);
6437    let sql = if projection.is_empty() && renamed.is_none() {
6438        format!(
6439            "DELETE FROM {}.{} AS {} {} RETURNING {}",
6440            postrust_sql::escape_ident(schema_name),
6441            postrust_sql::escape_ident(table_name),
6442            written,
6443            where_sql,
6444            row_json(&written, &column_types)
6445        )
6446    } else {
6447        format!(
6448            "WITH pgrst_deleted AS (DELETE FROM {}.{} AS {} {} RETURNING {}.*) \
6449             SELECT {} FROM (SELECT {}{} FROM pgrst_deleted AS src) AS pgrst_r",
6450            postrust_sql::escape_ident(schema_name),
6451            postrust_sql::escape_ident(table_name),
6452            written,
6453            where_sql,
6454            written,
6455            row_json("pgrst_r", &column_types),
6456            renamed.unwrap_or_else(|| "src.*".to_string()),
6457            projection
6458        )
6459    };
6460
6461    trace!("Executing DELETE SQL: {}", sql);
6462
6463    let mut query = sqlx::query(&sql);
6464    for val in &values {
6465        query = bind_json_value(query, val);
6466    }
6467
6468    let mut guard = write_tx(gql_ctx, pool).await?;
6469    let conn = guard.as_mut().expect("write_tx opens one");
6470    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6471
6472    let deleted: Vec<Value> = rows
6473        .iter()
6474        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6475        .map(json_to_value)
6476        .collect();
6477
6478    Ok(deleted)
6479}
6480
6481/// The predicate a role's permission adds to every read of one table.
6482///
6483/// `None` where nothing restricts the read: no permission document, an
6484/// administrator, or a permission whose filter is `{}`. The value is a
6485/// `<table>_bool_exp` like any other, because [`crate::role::permission_where`]
6486/// rewrites it into one -- which is what lets the same compiler serve both and
6487/// keeps a permission able to say everything a `where` can.
6488fn permission_predicate(
6489    caller: &crate::role::Caller<'_>,
6490    names: &crate::names::NameOverrides,
6491    schema: &str,
6492    table: &str,
6493) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6494    permission_filter(caller, names, schema, table, crate::role::Verb::Select)
6495}
6496
6497/// The same, for whichever grant the operation is asking under.
6498fn permission_filter(
6499    caller: &crate::role::Caller<'_>,
6500    names: &crate::names::NameOverrides,
6501    schema: &str,
6502    table: &str,
6503    verb: crate::role::Verb,
6504) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6505    crate::role::row_filter(caller, names, schema, table, verb)
6506        .map(|filter| filter.map(|predicate| serde_json::json!({ PERMISSION: predicate })))
6507        .map_err(|fault| coded_error(fault.code(), fault.to_string()))
6508}
6509
6510/// The key a permission's own predicate is wrapped in on its way to the
6511/// compiler.
6512///
6513/// The two halves of a `where` are the same language and are compiled by the
6514/// same code, which is what keeps a permission able to say everything a client
6515/// can. They are not evaluated by the same party: a client's half asks what
6516/// *this caller* may see, and a permission's half is the server's own
6517/// predicate, evaluated as the server.
6518///
6519/// The difference shows on a relationship. `{"Artist": {"id":
6520/// "X-Hasura-Artist-Id"}}` on `Track` grants the tracks belonging to the
6521/// caller's artist -- to a role that has no permission on `Artist` at all, and
6522/// is not meant to have one: the predicate consults that table, it does not
6523/// expose it. Compiled as the caller, it needs a permission that does not
6524/// exist and refuses itself; compiled as the server, it says what it was
6525/// written to say. `_exists` has always worked this way and says so where it
6526/// is built; this is the same rule for the relationship a permission follows.
6527///
6528/// The name cannot collide with a column: no boolean expression a client can
6529/// write has this field, because no input type declares it.
6530const PERMISSION: &str = "_permission";
6531
6532/// How many rows a permission lets one read return.
6533///
6534/// A ceiling rather than a default: a request asking for more gets this, one
6535/// asking for fewer gets what it asked for, and one asking for nothing gets
6536/// the ceiling. The same rule `PGRST_MAX_ROWS` follows, folded with it.
6537fn permission_limit(
6538    caller: &crate::role::Caller<'_>,
6539    names: &crate::names::NameOverrides,
6540    schema: &str,
6541    table: &str,
6542) -> Option<i64> {
6543    crate::role::read_limit(caller, names, schema, table)
6544}
6545
6546/// Both predicates, as one.
6547///
6548/// Written as an `_and` rather than as two clauses so that everything
6549/// downstream -- parameter numbering, relationship resolution, the `EXISTS`
6550/// that a relationship predicate becomes -- happens once, in one place, for
6551/// both halves.
6552fn and_predicates(
6553    request: Option<serde_json::Value>,
6554    permission: Option<serde_json::Value>,
6555) -> Option<serde_json::Value> {
6556    match (request, permission) {
6557        (None, None) => None,
6558        (Some(only), None) | (None, Some(only)) => Some(only),
6559        (Some(request), Some(permission)) => {
6560            Some(serde_json::json!({ "_and": [request, permission] }))
6561        }
6562    }
6563}
6564
6565/// Build a WHERE clause from a boolean expression.
6566///
6567/// The expression is the JSON form of a `<table>_bool_exp`: column names
6568/// mapping to comparisons, and `_and`, `_or` and `_not` for structure. It
6569/// nests, so this recurses.
6570///
6571/// Nothing is dropped silently. An operator that is not recognised widens the
6572/// result set if it is ignored -- every row for a query, every row for a
6573/// mutation -- so it is an error instead.
6574fn build_where_clause(
6575    where_value: Option<&serde_json::Value>,
6576    start_param_idx: usize,
6577    scope: &WhereScope<'_>,
6578) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6579    let mut values: Vec<serde_json::Value> = Vec::new();
6580    let mut param_idx = start_param_idx;
6581    let mut alias_counter = 0usize;
6582
6583    let condition = match where_value {
6584        Some(value) => build_condition(
6585            value,
6586            scope,
6587            &mut param_idx,
6588            &mut values,
6589            &mut alias_counter,
6590        )?,
6591        None => None,
6592    };
6593
6594    Ok(match condition {
6595        Some(sql) => (format!("WHERE {}", sql), values),
6596        None => (String::new(), values),
6597    })
6598}
6599
6600/// The table a boolean expression is being read against.
6601///
6602/// Column references are qualified with `sql_ref` so that a predicate over a
6603/// relationship -- which becomes a correlated `EXISTS` against another table --
6604/// can tell the two apart. Without the qualification a child column sharing a
6605/// parent column's name would silently resolve to whichever PostgreSQL
6606/// preferred.
6607///
6608/// `resolution` is absent where a caller has no schema cache to hand, which is
6609/// how the mutation paths are called. A relationship predicate then reports
6610/// that it cannot be resolved rather than being read as a column.
6611pub struct WhereScope<'a> {
6612    /// The table itself, for the comparisons that need a column's type.
6613    qualified: postrust_core::api_request::QualifiedIdentifier,
6614    /// How to refer to this table's columns in SQL.
6615    sql_ref: String,
6616    /// How to refer to this table's *row*, which is what a function taking the
6617    /// row is passed. A qualified name reads as a column reference there, so
6618    /// this is the bare alias.
6619    row_ref: String,
6620    /// The GraphQL type name, which is the key into the relationship map.
6621    type_name: String,
6622    /// How to refer to the columns of the table the predicate started at, and
6623    /// which table that is.
6624    ///
6625    /// A column comparison written `["$", "name"]` means that table's column,
6626    /// however many relationships the predicate has descended through since --
6627    /// which is what makes the form worth having. Equal to this table's own
6628    /// until an `EXISTS` is entered.
6629    root_ref: String,
6630    root_qualified: postrust_core::api_request::QualifiedIdentifier,
6631    /// The names this table's columns are exposed under, so a predicate
6632    /// written against a field can be written as SQL against a column.
6633    names: &'a crate::names::NameOverrides,
6634    resolution: Option<WhereResolution<'a>>,
6635    /// Who is asking, so that a predicate following a relationship narrows the
6636    /// far side to what that caller may read.
6637    ///
6638    /// Unrestricted unless a scope is told otherwise, which is what leaves
6639    /// every path that has no caller to hand -- the mutation ones -- behaving
6640    /// as it did.
6641    caller: Option<crate::role::Caller<'a>>,
6642}
6643
6644/// What a scope needs to follow a relationship.
6645struct WhereResolution<'a> {
6646    cache: &'a SchemaCache,
6647    relationships: &'a HashMap<String, Vec<RelationshipField>>,
6648}
6649
6650impl<'a> WhereScope<'a> {
6651    /// A scope over a table addressed by its qualified name.
6652    pub fn table(
6653        schema: &str,
6654        table: &str,
6655        type_name: &str,
6656        names: &'a crate::names::NameOverrides,
6657    ) -> Self {
6658        Self {
6659            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6660            sql_ref: format!(
6661                "{}.{}",
6662                postrust_sql::escape_ident(schema),
6663                postrust_sql::escape_ident(table)
6664            ),
6665            row_ref: postrust_sql::escape_ident(table),
6666            type_name: type_name.to_string(),
6667            root_ref: format!(
6668                "{}.{}",
6669                postrust_sql::escape_ident(schema),
6670                postrust_sql::escape_ident(table)
6671            ),
6672            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6673            names,
6674            resolution: None,
6675            caller: None,
6676        }
6677    }
6678
6679    /// The same, referred to by an alias rather than by its name.
6680    ///
6681    /// A statement that writes a table has to name that table's row in
6682    /// `RETURNING`, and a bare table name there is read as a *column* first --
6683    /// so `area.area`, a geography column in a table of the same name, made
6684    /// `to_jsonb("area")` the shape rather than the row. The alias is a name
6685    /// no column has.
6686    fn under_alias(mut self, alias: &str) -> Self {
6687        // The root moves with it: this is the outermost table being renamed,
6688        // not a step into another one.
6689        if self.root_ref == self.sql_ref {
6690            self.root_ref = postrust_sql::escape_ident(alias);
6691        }
6692        self.sql_ref = postrust_sql::escape_ident(alias);
6693        self.row_ref = postrust_sql::escape_ident(alias);
6694        self
6695    }
6696
6697    /// The same, able to follow relationships.
6698    fn with_resolution(
6699        mut self,
6700        cache: &'a SchemaCache,
6701        relationships: &'a HashMap<String, Vec<RelationshipField>>,
6702    ) -> Self {
6703        self.resolution = Some(WhereResolution {
6704            cache,
6705            relationships,
6706        });
6707        self
6708    }
6709
6710    /// A scope over an aliased table, able to follow relationships.
6711    #[allow(clippy::too_many_arguments)]
6712    fn for_alias(
6713        schema: &str,
6714        table: &str,
6715        alias: &str,
6716        type_name: &str,
6717        cache: &'a SchemaCache,
6718        relationships: &'a HashMap<String, Vec<RelationshipField>>,
6719        names: &'a crate::names::NameOverrides,
6720    ) -> Self {
6721        Self {
6722            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6723            sql_ref: postrust_sql::escape_ident(alias),
6724            row_ref: postrust_sql::escape_ident(alias),
6725            type_name: type_name.to_string(),
6726            root_ref: postrust_sql::escape_ident(alias),
6727            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6728            names,
6729            resolution: Some(WhereResolution {
6730                cache,
6731                relationships,
6732            }),
6733            caller: None,
6734        }
6735    }
6736
6737    /// A scope over an aliased table, for the inside of an `EXISTS`.
6738    /// A scope over the *other* table of a relationship, under an alias.
6739    ///
6740    /// The child's own qualified name, not the parent's: a comparison inside
6741    /// an `EXISTS` is against the child's columns, so it is the child's types
6742    /// and the child's renames that decide how to write it.
6743    fn aliased(
6744        schema: &str,
6745        table: &str,
6746        alias: &str,
6747        type_name: &str,
6748        from: &WhereScope<'a>,
6749    ) -> Self {
6750        Self {
6751            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6752            sql_ref: postrust_sql::escape_ident(alias),
6753            row_ref: postrust_sql::escape_ident(alias),
6754            type_name: type_name.to_string(),
6755            // Carried, not replaced: `$` names the table the predicate began
6756            // at, which is the whole point of descending with it.
6757            root_ref: from.root_ref.clone(),
6758            root_qualified: from.root_qualified.clone(),
6759            names: from.names,
6760            resolution: from.resolution.as_ref().map(|r| WhereResolution {
6761                cache: r.cache,
6762                relationships: r.relationships,
6763            }),
6764            // Carried across the relationship: the caller does not change on
6765            // the way into an EXISTS, and the far side has its own permission
6766            // to be narrowed by.
6767            caller: from.caller,
6768        }
6769    }
6770
6771    /// Narrow what this scope's predicates may reach to one caller.
6772    pub fn for_caller(mut self, caller: crate::role::Caller<'a>) -> Self {
6773        self.caller = Some(caller);
6774        self
6775    }
6776
6777    /// The same scope with no caller: what a permission's own predicate is
6778    /// evaluated as. See `PERMISSION`.
6779    fn as_system(&self) -> WhereScope<'a> {
6780        WhereScope {
6781            qualified: self.qualified.clone(),
6782            sql_ref: self.sql_ref.clone(),
6783            row_ref: self.row_ref.clone(),
6784            type_name: self.type_name.clone(),
6785            // The root moves here. `$` in a permission names the table the
6786            // permission is *written on*, which is this one -- not whatever
6787            // table the query happened to start at. Reading `dml_rbp_tasks`
6788            // through a project's `where: {tasks: {...}}` compared the task's
6789            // grant against a column of `dml_rbp_projects` until this, and
6790            // answered with rows the role may not see.
6791            root_ref: self.sql_ref.clone(),
6792            root_qualified: self.qualified.clone(),
6793            names: self.names,
6794            resolution: self.resolution.as_ref().map(|r| WhereResolution {
6795                cache: r.cache,
6796                relationships: r.relationships,
6797            }),
6798            caller: None,
6799        }
6800    }
6801
6802    /// What a field name refers to, as SQL: a column of this table, or the
6803    /// call that produces a computed field.
6804    ///
6805    /// A computed field is in the boolean expression because it is on the type
6806    /// -- `where: {sum_float_offset: {_gt: 5}}` is a question a client can
6807    /// write -- and reading it as a column answered `column
6808    /// float_test.sum_float_offset does not exist`.
6809    fn column(&self, name: &str) -> String {
6810        let column = self.column_name(name);
6811        let plain = format!("{}.{}", self.sql_ref, postrust_sql::escape_ident(column));
6812        let Some(cache) = self.resolution.as_ref().map(|r| r.cache) else {
6813            return plain;
6814        };
6815        let Some(table) = cache.get_table(&self.qualified) else {
6816            return plain;
6817        };
6818        if table.get_column(column).is_some() {
6819            return plain;
6820        }
6821        let function = self
6822            .names
6823            .computed_source(&table.schema, &table.name, name)
6824            .unwrap_or(name);
6825        match table.get_computed_column(function) {
6826            Some(definition) => {
6827                computed_call(definition, &format!("{}.*", self.row_ref), &self.row_ref)
6828            }
6829            None => plain,
6830        }
6831    }
6832
6833    /// The column behind a field name.
6834    fn column_name<'n>(&'n self, name: &'n str) -> &'n str {
6835        self.names
6836            .column_source(&self.qualified.schema, &self.qualified.name, name)
6837            .unwrap_or(name)
6838    }
6839
6840    /// The PostgreSQL type of one of this table's columns, where it can be
6841    /// found. A spatial comparison needs it: the same function takes a
6842    /// geometry or a geography and the operand has to be cast to match.
6843    fn column_type(&self, name: &str) -> Option<String> {
6844        let cache = self.resolution.as_ref()?.cache;
6845        let table = cache.get_table(&self.qualified)?;
6846        table
6847            .get_column(self.column_name(name))
6848            .map(|c| c.nominal_type.clone())
6849    }
6850
6851    /// The other side of a column-to-column comparison, as SQL.
6852    ///
6853    /// Hasura writes the operand two ways. A bare name is a column of *this*
6854    /// table -- `{"bid_price": {"$cgt": "price"}}` compares two columns of one
6855    /// auction. `["$", "name"]` is a column of the table the permission is
6856    /// written on, which is what makes the form useful inside a predicate over
6857    /// a relationship: `{"item": {"item_quantity": {"_cgte": ["$",
6858    /// "order_cart_quantity"]}}}` asks whether the item has as many in stock
6859    /// as the cart is asking for, and the cart is the outer row.
6860    ///
6861    /// Nothing else is accepted. A longer path would walk relationships to
6862    /// reach the column, which Hasura allows and no case here writes -- and
6863    /// reading it wrongly would widen a permission rather than narrow it.
6864    fn other_column(
6865        &self,
6866        operand: &serde_json::Value,
6867        op: &str,
6868    ) -> Result<String, async_graphql::Error> {
6869        use serde_json::Value;
6870
6871        let refused = |what: &str| {
6872            async_graphql::Error::new(format!(
6873                "the \"{}\" comparison takes the name of a column, {}",
6874                op, what
6875            ))
6876        };
6877        let (against, qualified, name) = match operand {
6878            Value::String(name) => (&self.sql_ref, &self.qualified, name.as_str()),
6879            Value::Array(path) => match path.as_slice() {
6880                [Value::String(name)] => (&self.sql_ref, &self.qualified, name.as_str()),
6881                [Value::String(root), Value::String(name)] if root == "$" => {
6882                    (&self.root_ref, &self.root_qualified, name.as_str())
6883                }
6884                _ => return Err(refused("or [\"$\", \"a column\"]")),
6885            },
6886            _ => return Err(refused("written as a string")),
6887        };
6888        // Read against whichever table it belongs to, since a rename is
6889        // per-table and the two are not the same table.
6890        let column = self
6891            .names
6892            .column_source(&qualified.schema, &qualified.name, name)
6893            .unwrap_or(name);
6894        Ok(format!(
6895            "{}.{}",
6896            against,
6897            postrust_sql::escape_ident(column)
6898        ))
6899    }
6900
6901    /// The relationship a predicate is following, where there is one.
6902    ///
6903    /// Looked up in the schema's own map first, which is the answer for every
6904    /// relationship the role can read. A permission may name one it cannot:
6905    /// role `Artist` reads `Track` and filters it by `{"Artist": {"id": ...}}`
6906    /// while having no permission on `Artist` at all, so that relationship is
6907    /// in no schema this role is served. The catalogue still has it -- the
6908    /// cache a scope resolves against is the unreduced one -- and the
6909    /// relationship is derived from it there.
6910    ///
6911    /// Deriving it cannot let a client reach a hidden table: a key that is not
6912    /// a field of the boolean expression is refused before any of this runs,
6913    /// and the only writer of such a key is a permission.
6914    fn relationship(&self, name: &str) -> Option<std::borrow::Cow<'a, RelationshipField>> {
6915        let resolution = self.resolution.as_ref()?;
6916        let published = resolution
6917            .relationships
6918            .get(&self.type_name)
6919            .and_then(|fields| fields.iter().find(|r| r.name == name));
6920        if let Some(found) = published {
6921            return Some(std::borrow::Cow::Borrowed(found));
6922        }
6923
6924        let table = resolution.cache.get_table(&self.qualified)?;
6925
6926        // A relationship metadata declares, which may be a mapped join the
6927        // catalogue has never heard of. `message.members` is one, and a
6928        // permission on `message` is written through it -- so this is not a
6929        // convenience: without it that filter cannot be compiled at all and
6930        // the read is refused rather than narrowed.
6931        if let Some(declared) = self
6932            .names
6933            .declared_relationships(&table.schema, &table.name)
6934        {
6935            if let Some(entry) = declared.iter().find(|entry| entry.name == name) {
6936                // Reached by a mapping, which the catalogue has never heard
6937                // of.
6938                let target = entry
6939                    .target(&table.schema)
6940                    .map(|(_, target)| target)
6941                    .unwrap_or_default();
6942                if let Some(field) = crate::schema::mapped_relationship_field(
6943                    entry,
6944                    table,
6945                    resolution.cache,
6946                    &table.schema,
6947                    &target,
6948                    &target,
6949                    &target,
6950                ) {
6951                    return Some(std::borrow::Cow::Owned(field));
6952                }
6953                // Or by a key, under the name the declaration gives it --
6954                // which is the only place that name is written down now that
6955                // a declaration carries it.
6956                if let Some(key) = entry.using.as_deref() {
6957                    if let Some(found) = resolution
6958                        .cache
6959                        .get_relationships(&self.qualified, &table.schema)
6960                        .into_iter()
6961                        .flatten()
6962                        .find(|rel| {
6963                            crate::schema::relationship_keys(rel)
6964                                .iter()
6965                                .any(|found| found == key)
6966                        })
6967                    {
6968                        return Some(std::borrow::Cow::Owned(
6969                            crate::schema::relationship::RelationshipField::from_relationship_named(
6970                                found,
6971                                &found.foreign_table().name,
6972                                Some(&entry.name),
6973                            ),
6974                        ));
6975                    }
6976                }
6977            }
6978        }
6979
6980        resolution
6981            .cache
6982            .get_relationships(&self.qualified, &table.schema)?
6983            .iter()
6984            .find_map(|rel| {
6985                let given = crate::schema::relationship_keys(rel)
6986                    .iter()
6987                    .find_map(|key| self.names.relationship(&table.schema, &table.name, key));
6988                // The target's own name stands in for its GraphQL type, which
6989                // this role has none of. Nothing reads it but the lookup
6990                // above, and that lookup will land here again.
6991                let field = RelationshipField::from_relationship_named(
6992                    rel,
6993                    &rel.foreign_table().name,
6994                    given,
6995                );
6996                (field.name == name).then_some(std::borrow::Cow::Owned(field))
6997            })
6998    }
6999}
7000
7001/// The SQL operator behind a column-to-column comparison, where this is one.
7002///
7003/// `_ceq`, `_cne`, `_cgt`, `_cgte`, `_clt`, `_clte`: the `c` is for column.
7004/// Only a permission writes these -- no boolean expression a client can write
7005/// has them, because a client has no way to name the row a comparison would be
7006/// against. A permission does: `{"bid_price": {"$cgt": "price"}}` on `auction`
7007/// grants the rows whose bid has passed the reserve, which is a fact about the
7008/// row rather than about the caller.
7009fn column_comparison(op: &str) -> Option<&'static str> {
7010    Some(match op {
7011        "_ceq" => "=",
7012        "_cne" => "<>",
7013        "_cgt" => ">",
7014        "_cgte" => ">=",
7015        "_clt" => "<",
7016        "_clte" => "<=",
7017        _ => return None,
7018    })
7019}
7020
7021/// One boolean expression, as SQL. `None` where it constrains nothing.
7022fn build_condition(
7023    value: &serde_json::Value,
7024    scope: &WhereScope<'_>,
7025    param_idx: &mut usize,
7026    values: &mut Vec<serde_json::Value>,
7027    alias_counter: &mut usize,
7028) -> Result<Option<String>, async_graphql::Error> {
7029    let serde_json::Value::Object(map) = value else {
7030        return Ok(None);
7031    };
7032
7033    let mut conditions: Vec<String> = Vec::new();
7034
7035    for (key, val) in map {
7036        match key.as_str() {
7037            // An empty `_and` is true and an empty `_or` is false, which is
7038            // what SQL's own identities give: nothing to AND constrains
7039            // nothing, nothing to OR matches nothing.
7040            "_and" | "_or" => {
7041                let members = val.as_array().ok_or_else(|| {
7042                    async_graphql::Error::new(format!("\"{}\" takes a list of expressions", key))
7043                })?;
7044                let mut parts = Vec::new();
7045                for member in members {
7046                    if let Some(sql) =
7047                        build_condition(member, scope, param_idx, values, alias_counter)?
7048                    {
7049                        parts.push(sql);
7050                    }
7051                }
7052                if parts.is_empty() {
7053                    if key == "_or" {
7054                        conditions.push("false".to_string());
7055                    }
7056                    continue;
7057                }
7058                let joiner = if key == "_and" { " AND " } else { " OR " };
7059                conditions.push(format!("({})", parts.join(joiner)));
7060            }
7061            "_not" => {
7062                if let Some(sql) = build_condition(val, scope, param_idx, values, alias_counter)? {
7063                    conditions.push(format!("NOT ({})", sql));
7064                }
7065            }
7066            // A permission's own predicate, which is the server's rather
7067            // than the caller's. See `PERMISSION`.
7068            PERMISSION => {
7069                if let Some(sql) =
7070                    build_condition(val, &scope.as_system(), param_idx, values, alias_counter)?
7071                {
7072                    conditions.push(sql);
7073                }
7074            }
7075            // A question about a table nothing relates this one to. Only a
7076            // permission writes it; no client can, because no boolean
7077            // expression has the field.
7078            "_exists" => {
7079                conditions.push(table_exists_sql(
7080                    val,
7081                    scope,
7082                    param_idx,
7083                    values,
7084                    alias_counter,
7085                )?);
7086            }
7087            // A relationship is filtered by filtering the rows at its other
7088            // end. `where: {articles: {title: {_eq: "x"}}}` keeps the authors
7089            // that have such an article -- an `EXISTS` correlated back to the
7090            // parent row, which is also what it means for a to-one
7091            // relationship, where the subquery simply cannot match twice.
7092            name if scope.relationship(name).is_some() => {
7093                let relationship = scope.relationship(name).expect("just checked");
7094                conditions.push(exists_sql(
7095                    &relationship,
7096                    val,
7097                    scope,
7098                    param_idx,
7099                    values,
7100                    alias_counter,
7101                )?);
7102            }
7103            // A question about the whole related set rather than about any
7104            // one row of it. `where: {articles_aggregate: {count: {predicate:
7105            // {_gt: 2}}}}` keeps the authors with more than two articles,
7106            // which no `EXISTS` over one article can say.
7107            name if scope.column_type(name).is_none()
7108                && name
7109                    .strip_suffix("_aggregate")
7110                    .and_then(|rel| scope.relationship(rel))
7111                    .is_some_and(|rel| rel.is_list) =>
7112            {
7113                let relationship = name
7114                    .strip_suffix("_aggregate")
7115                    .and_then(|rel| scope.relationship(rel))
7116                    .expect("just checked");
7117                conditions.push(aggregate_predicate_sql(
7118                    &relationship,
7119                    val,
7120                    scope,
7121                    param_idx,
7122                    values,
7123                    alias_counter,
7124                )?);
7125            }
7126            column => {
7127                let quoted = scope.column(column);
7128                match val {
7129                    serde_json::Value::Object(ops) => {
7130                        let column_type = scope.column_type(column);
7131                        for (op, operand) in ops {
7132                            // A comparison against another column rather than
7133                            // against a value, which only a permission can
7134                            // write. It needs the scope to say *which* column,
7135                            // so it is answered here rather than in
7136                            // `comparison_sql`, which sees one side.
7137                            if let Some(other) = column_comparison(op) {
7138                                conditions.push(format!(
7139                                    "{} {} {}",
7140                                    quoted,
7141                                    other,
7142                                    scope.other_column(operand, op)?
7143                                ));
7144                                continue;
7145                            }
7146                            conditions.push(comparison_sql(
7147                                &quoted,
7148                                column,
7149                                column_type.as_deref(),
7150                                op,
7151                                operand,
7152                                param_idx,
7153                                values,
7154                            )?);
7155                        }
7156                    }
7157                    // `{id: 1}` rather than `{id: {_eq: 1}}`. Not a spelling
7158                    // Hasura accepts, but one that costs nothing to read and
7159                    // that hand-written queries reach for.
7160                    other => {
7161                        conditions.push(format!("{} = ${}", quoted, param_idx));
7162                        values.push(other.clone());
7163                        *param_idx += 1;
7164                    }
7165                }
7166            }
7167        }
7168    }
7169
7170    Ok(if conditions.is_empty() {
7171        None
7172    } else if conditions.len() == 1 {
7173        conditions.pop()
7174    } else {
7175        Some(conditions.join(" AND "))
7176    })
7177}
7178
7179/// A predicate over a relationship, as a correlated `EXISTS`.
7180///
7181/// Only a plain key join is expressible this way. A many-to-many through a
7182/// junction and a computed relationship both need more than a pair of columns
7183/// to correlate on, and saying so is the only honest answer -- reading the
7184/// predicate as though it constrained nothing would return every parent row.
7185fn exists_sql(
7186    relationship: &RelationshipField,
7187    child_expression: &serde_json::Value,
7188    scope: &WhereScope<'_>,
7189    param_idx: &mut usize,
7190    values: &mut Vec<serde_json::Value>,
7191    alias_counter: &mut usize,
7192) -> Result<String, async_graphql::Error> {
7193    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7194        async_graphql::Error::new(format!(
7195            "filtering on the relationship \"{}\" is not available here",
7196            relationship.name
7197        ))
7198    })?;
7199
7200    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7201        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7202
7203    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7204        return Err(async_graphql::Error::new(format!(
7205            "filtering on \"{}\" is not supported: it is reached through a junction \
7206             rather than by a key",
7207            relationship.name
7208        )));
7209    }
7210
7211    *alias_counter += 1;
7212    let alias = format!("pgrst_rel_{}", alias_counter);
7213    let child_scope = WhereScope::aliased(
7214        &plan.foreign_schema,
7215        &plan.foreign_table,
7216        &alias,
7217        &relationship.target_type,
7218        scope,
7219    );
7220
7221    // A computed relationship is correlated by argument -- the function takes
7222    // the parent row -- where a key relationship is correlated by columns.
7223    let (source, mut correlation) = match &plan.function {
7224        Some(function) => (
7225            format!(
7226                "{}.{}({})",
7227                postrust_sql::escape_ident(&function.schema),
7228                postrust_sql::escape_ident(&function.name),
7229                scope.row_ref
7230            ),
7231            Vec::new(),
7232        ),
7233        None => {
7234            let mut columns = Vec::with_capacity(plan.columns.len());
7235            for (parent_column, child_column) in &plan.columns {
7236                columns.push(format!(
7237                    "{} = {}",
7238                    scope.column(parent_column),
7239                    child_scope.column(child_column)
7240                ));
7241            }
7242            (
7243                format!(
7244                    "{}.{}",
7245                    postrust_sql::escape_ident(&plan.foreign_schema),
7246                    postrust_sql::escape_ident(&plan.foreign_table)
7247                ),
7248                columns,
7249            )
7250        }
7251    };
7252
7253    let child_condition = build_condition(
7254        child_expression,
7255        &child_scope,
7256        param_idx,
7257        values,
7258        alias_counter,
7259    )?;
7260    if let Some(sql) = child_condition {
7261        correlation.push(format!("({})", sql));
7262    }
7263
7264    // The far side's own permission. `where: {author: {name: {_eq: "Ann"}}}`
7265    // asks whether a related row exists, and a row this caller may not read is
7266    // one it may not learn the existence of either -- so the child's filter
7267    // narrows the EXISTS exactly as it narrows a read of the same table.
7268    if let Some(caller) = &child_scope.caller {
7269        // Through `permission_predicate` rather than `role::read_predicate`,
7270        // so the far side's filter is marked as the server's own -- see
7271        // `PERMISSION`. Without the mark it was compiled as the caller, and a
7272        // filter that follows a relationship of its own then needed a
7273        // permission on *that* table: `dml_rbp_tasks` is filtered through
7274        // `grants`, which the role reading tasks may not read.
7275        let permission = permission_predicate(
7276            caller,
7277            child_scope.names,
7278            &plan.foreign_schema,
7279            &plan.foreign_table,
7280        )?;
7281        if let Some(predicate) = permission {
7282            if let Some(sql) =
7283                build_condition(&predicate, &child_scope, param_idx, values, alias_counter)?
7284            {
7285                correlation.push(format!("({})", sql));
7286            }
7287        }
7288    }
7289    if correlation.is_empty() {
7290        correlation.push("true".to_string());
7291    }
7292
7293    Ok(format!(
7294        "EXISTS (SELECT 1 FROM {} AS {} WHERE {})",
7295        source,
7296        postrust_sql::escape_ident(&alias),
7297        correlation.join(" AND ")
7298    ))
7299}
7300
7301/// `_exists`, the predicate only a permission can write, as SQL.
7302///
7303/// ```json
7304/// {"_exists": {"_table": "user", "_where": {"id": "X-Hasura-User-Id",
7305///                                           "is_admin": true}}}
7306/// ```
7307///
7308/// It asks whether a row exists in *another* table -- one no foreign key
7309/// relates to this one. That is how Hasura writes "the caller is an
7310/// administrator" without asking the caller to say so: the row that decides it
7311/// lives in a table of its own, and whether the account this predicate guards
7312/// is readable does not depend on which account it is.
7313///
7314/// So the subselect is **uncorrelated**, which is the whole difference from
7315/// the `EXISTS` a relationship predicate builds. There are no key columns to
7316/// join on -- if there were, the permission would have been written as a
7317/// relationship -- and the `_where` is read against the named table's columns
7318/// alone.
7319///
7320/// The caller's own permissions are not applied inside it, and that is not an
7321/// oversight. The point of `_exists` is to consult a table the role has no
7322/// access to: in Hasura's corpus the role that reads `account` through this
7323/// predicate may not read `public.user` at all. Narrowing the subselect to
7324/// what the caller may read would make the predicate refuse itself.
7325fn table_exists_sql(
7326    spec: &serde_json::Value,
7327    scope: &WhereScope<'_>,
7328    param_idx: &mut usize,
7329    values: &mut Vec<serde_json::Value>,
7330    alias_counter: &mut usize,
7331) -> Result<String, async_graphql::Error> {
7332    let Some((schema, table)) = crate::role::exists_target(spec, &scope.qualified.schema) else {
7333        return Err(async_graphql::Error::new(
7334            "\"_exists\" needs a \"_table\" to look in",
7335        ));
7336    };
7337
7338    // The name comes from a permission document rather than from a request,
7339    // but it still reaches SQL, and a table that is not there would reach it as
7340    // a syntax error at the far end rather than as an answer here.
7341    let qualified = postrust_core::api_request::QualifiedIdentifier::new(&schema, &table);
7342    if scope
7343        .resolution
7344        .as_ref()
7345        .is_some_and(|r| r.cache.get_table(&qualified).is_none())
7346    {
7347        return Err(async_graphql::Error::new(format!(
7348            "\"_exists\" names a table this server does not have: \"{}.{}\"",
7349            schema, table
7350        )));
7351    }
7352
7353    *alias_counter += 1;
7354    let alias = format!("pgrst_exists_{}", alias_counter);
7355    // The type this table is exposed as, which is the key into the
7356    // relationship map -- so a `_where` may follow the named table's own
7357    // relationships, the way any other predicate over it could.
7358    let type_name = scope
7359        .names
7360        .base_name(&schema, &table)
7361        .unwrap_or(&table)
7362        .to_string();
7363    let mut inner = WhereScope::aliased(&schema, &table, &alias, &type_name, scope);
7364    // See above: the predicate is the permission, so it is not itself
7365    // subject to one.
7366    inner.caller = None;
7367
7368    let condition = match spec.get("_where") {
7369        Some(predicate) => build_condition(predicate, &inner, param_idx, values, alias_counter)?,
7370        None => None,
7371    };
7372
7373    Ok(format!(
7374        "EXISTS (SELECT 1 FROM {}.{} AS {} WHERE {})",
7375        postrust_sql::escape_ident(&schema),
7376        postrust_sql::escape_ident(&table),
7377        postrust_sql::escape_ident(&alias),
7378        condition.unwrap_or_else(|| "true".to_string())
7379    ))
7380}
7381
7382/// A predicate over an aggregate of a related set, as SQL.
7383///
7384/// `{count: {predicate: {_gt: 2}, filter: {...}}}` becomes a scalar subselect
7385/// correlated back to the parent row, compared the way any column is:
7386///
7387/// ```text
7388/// (SELECT count(*) FROM "public"."article" AS pgrst_rel_1
7389///   WHERE "author"."id" = pgrst_rel_1."author_id" AND (...)) > $1
7390/// ```
7391///
7392/// A scalar subselect rather than an `EXISTS`, because the answer is a number
7393/// or a truth value and not whether a row is there -- and because over no rows
7394/// at all `count` is zero and `bool_and` is null, which is what SQL says and
7395/// what a client asking "no articles" is relying on.
7396fn aggregate_predicate_sql(
7397    relationship: &RelationshipField,
7398    expression: &serde_json::Value,
7399    scope: &WhereScope<'_>,
7400    param_idx: &mut usize,
7401    values: &mut Vec<serde_json::Value>,
7402    alias_counter: &mut usize,
7403) -> Result<String, async_graphql::Error> {
7404    let serde_json::Value::Object(over) = expression else {
7405        return Ok("true".to_string());
7406    };
7407
7408    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7409        async_graphql::Error::new(format!(
7410            "filtering on the relationship \"{}\" is not available here",
7411            relationship.name
7412        ))
7413    })?;
7414    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7415        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7416    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7417        return Err(async_graphql::Error::new(format!(
7418            "aggregating over \"{}\" is not supported: it is reached through a junction \
7419             rather than by a key",
7420            relationship.name
7421        )));
7422    }
7423
7424    let mut conditions: Vec<String> = Vec::new();
7425    for (function, spec) in over {
7426        if spec.is_null() {
7427            continue;
7428        }
7429        let serde_json::Value::Object(spec) = spec else {
7430            continue;
7431        };
7432        let Some(predicate) = spec.get("predicate").filter(|p| !p.is_null()) else {
7433            return Err(validation_error(format!(
7434                "\"{}\" over \"{}\" needs a predicate",
7435                function, relationship.name
7436            )));
7437        };
7438
7439        *alias_counter += 1;
7440        let alias = format!("pgrst_agg_{}", alias_counter);
7441        let child_scope = WhereScope::aliased(
7442            &plan.foreign_schema,
7443            &plan.foreign_table,
7444            &alias,
7445            &relationship.target_type,
7446            scope,
7447        );
7448
7449        // Correlated the way an `EXISTS` over the same relationship is: by
7450        // the key, or by handing the parent row to the function.
7451        let (source, mut correlation) = match &plan.function {
7452            Some(routine) => (
7453                format!(
7454                    "{}.{}({})",
7455                    postrust_sql::escape_ident(&routine.schema),
7456                    postrust_sql::escape_ident(&routine.name),
7457                    scope.row_ref
7458                ),
7459                Vec::new(),
7460            ),
7461            None => {
7462                let mut columns = Vec::with_capacity(plan.columns.len());
7463                for (parent_column, child_column) in &plan.columns {
7464                    columns.push(format!(
7465                        "{} = {}",
7466                        scope.column(parent_column),
7467                        child_scope.column(child_column)
7468                    ));
7469                }
7470                (
7471                    format!(
7472                        "{}.{}",
7473                        postrust_sql::escape_ident(&plan.foreign_schema),
7474                        postrust_sql::escape_ident(&plan.foreign_table)
7475                    ),
7476                    columns,
7477                )
7478            }
7479        };
7480        // `filter` narrows what is aggregated, and is an ordinary boolean
7481        // expression over the child.
7482        if let Some(filter) = spec.get("filter").filter(|f| !f.is_null()) {
7483            if let Some(sql) =
7484                build_condition(filter, &child_scope, param_idx, values, alias_counter)?
7485            {
7486                correlation.push(format!("({})", sql));
7487            }
7488        }
7489        if correlation.is_empty() {
7490            correlation.push("true".to_string());
7491        }
7492
7493        let arguments = spec.get("arguments").filter(|a| !a.is_null());
7494        let distinct = matches!(spec.get("distinct"), Some(serde_json::Value::Bool(true)));
7495        let (aggregate, operand_type) = match function.as_str() {
7496            "count" => {
7497                let columns: Vec<String> = match arguments {
7498                    Some(serde_json::Value::Array(items)) => items
7499                        .iter()
7500                        .filter_map(|item| item.as_str())
7501                        .map(|column| child_scope.column(column))
7502                        .collect(),
7503                    Some(serde_json::Value::String(one)) => vec![child_scope.column(one)],
7504                    _ => Vec::new(),
7505                };
7506                let counted = match columns.len() {
7507                    0 => "*".to_string(),
7508                    1 => columns.into_iter().next().unwrap_or_default(),
7509                    _ => format!("({})", columns.join(", ")),
7510                };
7511                let counted = match distinct && counted != "*" {
7512                    true => format!("DISTINCT {}", counted),
7513                    false => counted,
7514                };
7515                (format!("count({})", counted), "integer")
7516            }
7517            "bool_and" | "bool_or" => {
7518                let Some(column) = arguments.and_then(|a| a.as_str()) else {
7519                    return Err(validation_error(format!(
7520                        "\"{}\" over \"{}\" needs the column to fold",
7521                        function, relationship.name
7522                    )));
7523                };
7524                (
7525                    format!("{}({})", function, child_scope.column(column)),
7526                    "boolean",
7527                )
7528            }
7529            other => {
7530                return Err(validation_error(format!(
7531                    "\"{}\" is not an aggregate a predicate can be written over",
7532                    other
7533                )))
7534            }
7535        };
7536
7537        let subselect = format!(
7538            "(SELECT {} FROM {} AS {} WHERE {})",
7539            aggregate,
7540            source,
7541            postrust_sql::escape_ident(&alias),
7542            correlation.join(" AND ")
7543        );
7544        let serde_json::Value::Object(operators) = predicate else {
7545            return Err(validation_error(format!(
7546                "the predicate on \"{}\" is not a comparison",
7547                function
7548            )));
7549        };
7550        for (operator, operand) in operators {
7551            conditions.push(comparison_sql(
7552                &subselect,
7553                function,
7554                Some(operand_type),
7555                operator,
7556                operand,
7557                param_idx,
7558                values,
7559            )?);
7560        }
7561    }
7562
7563    Ok(match conditions.is_empty() {
7564        true => "true".to_string(),
7565        false => format!("({})", conditions.join(" AND ")),
7566    })
7567}
7568
7569/// One spatial comparison, as a PostGIS call.
7570///
7571/// The operand arrives as GeoJSON, which is what Hasura accepts and what a
7572/// client sends, so it is parsed by `ST_GeomFromGeoJSON` rather than bound as
7573/// a shape. The cast that follows is the column's own type: the same function
7574/// takes a geometry or a geography and picking the wrong one is not an
7575/// overload PostGIS has.
7576#[allow(clippy::too_many_arguments)]
7577fn postgis_sql(
7578    quoted: &str,
7579    column_type: Option<&str>,
7580    function: &str,
7581    op: &str,
7582    operand: &serde_json::Value,
7583    param_idx: &mut usize,
7584    values: &mut Vec<serde_json::Value>,
7585) -> Result<String, async_graphql::Error> {
7586    let is_geography = column_type == Some("geography");
7587    let mut shape = |value: &serde_json::Value, param_idx: &mut usize| -> String {
7588        let placeholder = format!("${}", param_idx);
7589        *param_idx += 1;
7590        values.push(value.clone());
7591        match is_geography {
7592            true => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
7593            false => format!("ST_GeomFromGeoJSON({})", placeholder),
7594        }
7595    };
7596
7597    match op {
7598        // `{distance, from}`: the shape and how far from it.
7599        "_st_d_within" | "_st_3d_d_within" => {
7600            let serde_json::Value::Object(spec) = operand else {
7601                return Err(async_graphql::Error::new(format!(
7602                    "\"{}\" takes {{distance, from}}",
7603                    op
7604                )));
7605            };
7606            let Some(from) = spec.get("from") else {
7607                return Err(async_graphql::Error::new(format!(
7608                    "\"{}\" needs a shape to measure from",
7609                    op
7610                )));
7611            };
7612            let Some(distance) = spec.get("distance") else {
7613                return Err(async_graphql::Error::new(format!(
7614                    "\"{}\" needs a distance",
7615                    op
7616                )));
7617            };
7618            let from_sql = shape(from, param_idx);
7619            let distance_sql = format!("${}", param_idx);
7620            *param_idx += 1;
7621            values.push(distance.clone());
7622            // On a sphere the answer depends on which sphere: `ST_DWithin`
7623            // over geography measures on the spheroid by default and on a
7624            // perfect sphere when told not to, and the two disagree by enough
7625            // to change which rows come back. Geometry has no such argument.
7626            let spheroid = match (is_geography, spec.get("use_spheroid")) {
7627                (true, Some(use_spheroid)) if !use_spheroid.is_null() => {
7628                    let placeholder = format!("${}", param_idx);
7629                    *param_idx += 1;
7630                    values.push(use_spheroid.clone());
7631                    format!(", {}::boolean", placeholder)
7632                }
7633                _ => String::new(),
7634            };
7635            Ok(format!(
7636                "{}({}, {}, {}::float8{})",
7637                function, quoted, from_sql, distance_sql, spheroid
7638            ))
7639        }
7640        // A raster against another raster, bound as one rather than parsed.
7641        "_st_intersects_rast" => {
7642            let placeholder = format!("${}", param_idx);
7643            *param_idx += 1;
7644            values.push(operand.clone());
7645            Ok(format!("{}({}, {}::raster)", function, quoted, placeholder))
7646        }
7647        // A raster against a shape, in one band or in any.
7648        "_st_intersects_geom_nband" | "_st_intersects_nband_geom" => {
7649            let serde_json::Value::Object(spec) = operand else {
7650                return Err(async_graphql::Error::new(format!(
7651                    "\"{}\" takes an object",
7652                    op
7653                )));
7654            };
7655            let Some(geometry) = spec.get("geommin") else {
7656                return Err(async_graphql::Error::new(format!(
7657                    "\"{}\" needs a shape",
7658                    op
7659                )));
7660            };
7661            let band = spec.get("nband").filter(|v| !v.is_null());
7662            let geometry_sql = shape(geometry, param_idx);
7663            Ok(match band {
7664                None => format!("{}({}, {})", function, quoted, geometry_sql),
7665                Some(band) => {
7666                    let placeholder = format!("${}", param_idx);
7667                    *param_idx += 1;
7668                    values.push(band.clone());
7669                    // `ST_Intersects(raster, nband, geometry)` is the spelling
7670                    // with a band; the argument order is PostGIS's, not the
7671                    // input's.
7672                    format!(
7673                        "{}({}, {}::int, {})",
7674                        function, quoted, placeholder, geometry_sql
7675                    )
7676                }
7677            })
7678        }
7679        // Everything else is the relation between this shape and one other.
7680        _ => {
7681            let other = shape(operand, param_idx);
7682            Ok(format!("{}({}, {})", function, quoted, other))
7683        }
7684    }
7685}
7686
7687/// A list of values as an array PostgreSQL will read.
7688///
7689/// Bound one element at a time rather than as one parameter. A JSON array
7690/// arrives as the text `["a","b"]`, which is not an array literal -- PostgreSQL
7691/// answers `malformed array literal` and means it.
7692fn sql_array(
7693    items: &[serde_json::Value],
7694    cast: &str,
7695    param_idx: &mut usize,
7696    values: &mut Vec<serde_json::Value>,
7697) -> String {
7698    if items.is_empty() {
7699        return format!("ARRAY[]::{}", cast);
7700    }
7701    let placeholders: Vec<String> = items
7702        .iter()
7703        .map(|item| {
7704            let placeholder = format!("${}", param_idx);
7705            *param_idx += 1;
7706            values.push(item.clone());
7707            placeholder
7708        })
7709        .collect();
7710    format!("ARRAY[{}]::{}", placeholders.join(", "), cast)
7711}
7712
7713/// One comparison against one column.
7714#[allow(clippy::too_many_arguments)]
7715fn comparison_sql(
7716    quoted: &str,
7717    column: &str,
7718    column_type: Option<&str>,
7719    op: &str,
7720    operand: &serde_json::Value,
7721    param_idx: &mut usize,
7722    values: &mut Vec<serde_json::Value>,
7723) -> Result<String, async_graphql::Error> {
7724    // `_cast` is not a comparison but a change of what is being compared: the
7725    // column becomes another type and the comparisons inside it apply to that.
7726    // A geometry and a geography answer different questions about the same
7727    // shape -- one on a plane, one on a sphere -- and this is how a client asks
7728    // for the other without the schema carrying two columns.
7729    if op == "_cast" {
7730        let serde_json::Value::Object(casts) = operand else {
7731            return Err(async_graphql::Error::new(
7732                "_cast takes an object naming the type to compare as",
7733            ));
7734        };
7735        let mut conditions = Vec::new();
7736        for (target, comparisons) in casts {
7737            // The GraphQL name of what is being compared as, and the
7738            // PostgreSQL type behind it. `String` is the one that differs:
7739            // a document compared as text is compared as `text`.
7740            let pg_type = match target.as_str() {
7741                "geometry" | "geography" => target.as_str(),
7742                "String" => "text",
7743                _ => {
7744                    return Err(async_graphql::Error::new(format!(
7745                        "cannot compare \"{}\" as \"{}\"",
7746                        column, target
7747                    )))
7748                }
7749            };
7750            let serde_json::Value::Object(ops) = comparisons else {
7751                continue;
7752            };
7753            let cast = format!("{}::{}", quoted, pg_type);
7754            for (nested_op, nested_operand) in ops {
7755                conditions.push(comparison_sql(
7756                    &cast,
7757                    column,
7758                    Some(pg_type),
7759                    nested_op,
7760                    nested_operand,
7761                    param_idx,
7762                    values,
7763                )?);
7764            }
7765        }
7766        return Ok(match conditions.len() {
7767            0 => "true".to_string(),
7768            1 => conditions.pop().expect("just counted"),
7769            _ => format!("({})", conditions.join(" AND ")),
7770        });
7771    }
7772
7773    // A spatial relation is a function of two shapes rather than an operator
7774    // between them, so it is written before the operator table is consulted.
7775    if let Some(function) = crate::input::bool_exp::postgis_function(op) {
7776        return postgis_sql(
7777            quoted,
7778            column_type,
7779            function,
7780            op,
7781            operand,
7782            param_idx,
7783            values,
7784        );
7785    }
7786
7787    // A tree comparison is an operator, but one whose operand has to be cast:
7788    // `?` is "any of these labels" for an ltree and "has this key" for a
7789    // jsonb, and PostgreSQL tells them apart by the operand's type alone.
7790    if let Some((operator, cast)) = crate::input::bool_exp::ltree_operator(op) {
7791        if cast.ends_with("[]") {
7792            let items = operand.as_array().ok_or_else(|| {
7793                async_graphql::Error::new(format!(
7794                    "the \"{}\" comparison on \"{}\" takes a list",
7795                    op, column
7796                ))
7797            })?;
7798            return Ok(format!(
7799                "{} {} {}",
7800                quoted,
7801                operator,
7802                sql_array(items, cast, param_idx, values)
7803            ));
7804        }
7805        let placeholder = format!("${}", param_idx);
7806        *param_idx += 1;
7807        values.push(operand.clone());
7808        return Ok(format!("{} {} {}::{}", quoted, operator, placeholder, cast));
7809    }
7810
7811    // The path language is a query over the document rather than a comparison
7812    // against one, so it is a function of the column and the path: `_exists`
7813    // asks whether the path selects anything, `_match` whether the predicate
7814    // it ends in holds.
7815    if let Some(function) = match op {
7816        "_jsonb_path_exists" => Some("jsonb_path_exists"),
7817        "_jsonb_path_match" => Some("jsonb_path_match"),
7818        _ => None,
7819    } {
7820        let placeholder = format!("${}", param_idx);
7821        *param_idx += 1;
7822        values.push(operand.clone());
7823        return Ok(format!(
7824            "{}({}, {}::jsonpath)",
7825            function, quoted, placeholder
7826        ));
7827    }
7828
7829    // Comparisons binding exactly one parameter, by the SQL they become.
7830    let binary = match op {
7831        "_eq" => Some("="),
7832        "_neq" => Some("<>"),
7833        "_gt" => Some(">"),
7834        "_gte" => Some(">="),
7835        "_lt" => Some("<"),
7836        "_lte" => Some("<="),
7837        "_like" => Some("LIKE"),
7838        "_nlike" => Some("NOT LIKE"),
7839        "_ilike" => Some("ILIKE"),
7840        "_nilike" => Some("NOT ILIKE"),
7841        "_similar" => Some("SIMILAR TO"),
7842        "_nsimilar" => Some("NOT SIMILAR TO"),
7843        "_regex" => Some("~"),
7844        "_nregex" => Some("!~"),
7845        "_iregex" => Some("~*"),
7846        "_niregex" => Some("!~*"),
7847        "_contains" => Some("@>"),
7848        "_contained_in" => Some("<@"),
7849        "_has_key" => Some("?"),
7850        _ => None,
7851    };
7852
7853    if let Some(sql_op) = binary {
7854        let placeholder = format!("${}", param_idx);
7855        *param_idx += 1;
7856        // A containment operand is a whole document, and `"latest"` is one --
7857        // a JSON string, which is not the same text as `latest`. Binding the
7858        // bare string answered `invalid input syntax for type json`, so what
7859        // goes over the wire is the value as it is written in JSON.
7860        values.push(match op {
7861            "_contains" | "_contained_in" => match operand {
7862                serde_json::Value::String(_)
7863                | serde_json::Value::Number(_)
7864                | serde_json::Value::Bool(_) => serde_json::Value::String(operand.to_string()),
7865                other => other.clone(),
7866            },
7867            _ => operand.clone(),
7868        });
7869        // A bound parameter arrives as text and PostgreSQL infers a type for
7870        // it from the operator, which works only while the inference is
7871        // unambiguous. `jsonb @> text` is not an operator at all; and `id =
7872        // $1` against an integer column answers `operator does not exist:
7873        // integer = text` the moment the value is one PostgreSQL cannot read
7874        // as a number -- a null, most obviously, which is how a client asks a
7875        // question it expects an empty answer to.
7876        //
7877        // So the operand says what it is. The containment and key operators
7878        // name the type the operator needs; everything else names the
7879        // column's, which is the type it is being compared against.
7880        let cast = match op {
7881            "_contains" | "_contained_in" => "::jsonb".to_string(),
7882            "_has_key" => "::text".to_string(),
7883            // Only where inference would otherwise pick text and be wrong. A
7884            // cast tells PostgreSQL what the *result* is, and it infers the
7885            // parameter feeding it as text -- so `$1::int4` bound with a
7886            // number sends binary int8 bytes into a text parameter and
7887            // answers `invalid byte sequence for encoding "UTF8"`. A value
7888            // PostgreSQL can read as a number needs no help; a null is the one
7889            // that does, because `id = $1` with nothing to infer from is
7890            // `integer = text`.
7891            _ if operand.is_null() => match column_type {
7892                Some(pg_type) => format!("::{}", pg_type),
7893                None => String::new(),
7894            },
7895            // A parameter arrives as `text`, and PostgreSQL then resolves the
7896            // operator by taking the *column* down to text where it can --
7897            // which is the wrong operator, and sometimes no operator at all.
7898            // `citext = text` compares case-sensitively against a
7899            // case-insensitive column; `uuid = text` does not exist. So a
7900            // written value names the type it is being compared against.
7901            //
7902            // Only a string: a number bound with a cast is sent as binary into
7903            // a parameter PostgreSQL has inferred as text, which is the
7904            // `invalid byte sequence for encoding "UTF8"` this used to answer.
7905            _ if operand.is_string() => match column_type {
7906                Some(pg_type) => format!("::{}", pg_type),
7907                None => String::new(),
7908            },
7909            _ => String::new(),
7910        };
7911        return Ok(format!("{} {} {}{}", quoted, sql_op, placeholder, cast));
7912    }
7913
7914    match op {
7915        "_is_null" => Ok(if operand.as_bool().unwrap_or(false) {
7916            format!("{} IS NULL", quoted)
7917        } else {
7918            format!("{} IS NOT NULL", quoted)
7919        }),
7920        "_in" | "_nin" => {
7921            let items = operand.as_array().ok_or_else(|| {
7922                async_graphql::Error::new(format!(
7923                    "the \"{}\" comparison on \"{}\" requires a list of values",
7924                    op, column
7925                ))
7926            })?;
7927            if items.is_empty() {
7928                // `IN ()` is not valid SQL. An empty `_in` matches nothing and
7929                // an empty `_nin` matches everything.
7930                return Ok(if op == "_in" { "false" } else { "true" }.to_string());
7931            }
7932            let mut placeholders = Vec::with_capacity(items.len());
7933            for item in items {
7934                // Cast only a null, for the reason the binary comparisons
7935                // give: a cast makes PostgreSQL infer the parameter as text.
7936                let cast = match (item.is_null() || item.is_string(), column_type) {
7937                    (true, Some(pg_type)) => format!("::{}", pg_type),
7938                    _ => String::new(),
7939                };
7940                placeholders.push(format!("${}{}", param_idx, cast));
7941                values.push(item.clone());
7942                *param_idx += 1;
7943            }
7944            Ok(format!(
7945                "{} {} ({})",
7946                quoted,
7947                if op == "_in" { "IN" } else { "NOT IN" },
7948                placeholders.join(", ")
7949            ))
7950        }
7951        "_has_keys_any" | "_has_keys_all" => {
7952            let items = operand.as_array().ok_or_else(|| {
7953                async_graphql::Error::new(format!(
7954                    "the \"{}\" comparison on \"{}\" requires a list of keys",
7955                    op, column
7956                ))
7957            })?;
7958            Ok(format!(
7959                "{} {} {}",
7960                quoted,
7961                if op == "_has_keys_any" { "?|" } else { "?&" },
7962                sql_array(items, "text[]", param_idx, values)
7963            ))
7964        }
7965        other => Err(async_graphql::Error::new(format!(
7966            "unsupported comparison \"{}\" on \"{}\"",
7967            other, column
7968        ))),
7969    }
7970}
7971
7972/// Build an `ORDER BY` clause from the `order_by` argument.
7973///
7974/// The argument is a list of single-column objects -- `[{name: asc}, {id:
7975/// desc}]` -- and the list is ordered, which is why it is a list and not one
7976/// object with several keys. Column names are checked against the table in the
7977/// schema cache before being quoted, so a name that is unknown, or crafted to
7978/// inject SQL, is rejected rather than interpolated. Returns an empty string
7979/// when no ordering was requested.
7980#[allow(clippy::too_many_arguments)] // one parameter per SQL clause
7981async fn build_order_by_clause(
7982    ctx: &ResolverContext<'_>,
7983    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
7984    schema_name: &str,
7985    table_name: &str,
7986    type_name: &str,
7987    relationships: &HashMap<String, Vec<RelationshipField>>,
7988    reference: &str,
7989    names: &crate::names::NameOverrides,
7990) -> Result<String, async_graphql::Error> {
7991    let Ok(order_arg) = ctx.args.try_get("order_by") else {
7992        return Ok(String::new());
7993    };
7994
7995    let value = accessor_to_json(&order_arg);
7996    // A single object is accepted as a one-entry list, which is what a client
7997    // writing `order_by: {name: asc}` means and what Hasura reads it as.
7998    let entries: Vec<&serde_json::Value> = match &value {
7999        serde_json::Value::Array(items) => items.iter().collect(),
8000        serde_json::Value::Object(_) => vec![&value],
8001        serde_json::Value::Null => return Ok(String::new()),
8002        _ => {
8003            return Err(async_graphql::Error::new(
8004                "order_by takes an object or a list of them",
8005            ))
8006        }
8007    };
8008    if entries.is_empty() {
8009        return Ok(String::new());
8010    }
8011
8012    let guard = schema_cache
8013        .get()
8014        .await
8015        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8016    let cache = guard
8017        .as_ref()
8018        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8019    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8020    let table = cache
8021        .get_table(&qi)
8022        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8023
8024    let mut terms = Vec::new();
8025    let mut alias_counter = 0usize;
8026    for entry in entries {
8027        order_terms_into(
8028            entry,
8029            cache,
8030            relationships,
8031            type_name,
8032            table,
8033            reference,
8034            names,
8035            &mut alias_counter,
8036            &mut terms,
8037        )?;
8038    }
8039
8040    if terms.is_empty() {
8041        return Ok(String::new());
8042    }
8043    Ok(format!(" ORDER BY {}", terms.join(", ")))
8044}
8045
8046/// Collect the `ORDER BY` terms one entry contributes.
8047///
8048/// A key whose value is a direction is a column of this table. A key whose
8049/// value is an object is something the row points at, and ordering by it is a
8050/// correlated subselect: one related row contributes its column, and many
8051/// contribute an aggregate. That is the whole difference between the two
8052/// sides, and PostgreSQL will take a scalar subquery anywhere a column goes.
8053#[allow(clippy::too_many_arguments)]
8054fn order_terms_into(
8055    entry: &serde_json::Value,
8056    cache: &SchemaCache,
8057    relationships: &HashMap<String, Vec<RelationshipField>>,
8058    type_name: &str,
8059    table: &postrust_core::schema_cache::Table,
8060    reference: &str,
8061    names: &crate::names::NameOverrides,
8062    alias_counter: &mut usize,
8063    terms: &mut Vec<String>,
8064) -> Result<(), async_graphql::Error> {
8065    let serde_json::Value::Object(map) = entry else {
8066        return Err(async_graphql::Error::new(
8067            "each order_by entry is an object mapping a column to a direction",
8068        ));
8069    };
8070    let available: &[RelationshipField] = relationships
8071        .get(type_name)
8072        .map(|r| r.as_slice())
8073        .unwrap_or(&[]);
8074
8075    for (key, value) in map {
8076        // A direction given as null is no direction. `order_by: {id:
8077        // $direction}` with nothing bound for `$direction` is how a client
8078        // makes an ordering optional without writing the query twice, and the
8079        // same goes for `{author: $author_order_by}`, which is a whole
8080        // ordering left out.
8081        if value.is_null() {
8082            continue;
8083        }
8084
8085        // A direction: this table's own column, or a function of its row.
8086        if let Some(name) = value.as_str() {
8087            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8088                async_graphql::Error::new(format!(
8089                    "\"{}\" is not a sort direction; expected one of asc, desc, \
8090                     asc_nulls_first, asc_nulls_last, desc_nulls_first, desc_nulls_last",
8091                    name
8092                ))
8093            })?;
8094            let column = table_column_for(names, table, key);
8095            if table.get_column(column).is_some() {
8096                terms.push(format!(
8097                    "{}.{} {}",
8098                    reference,
8099                    postrust_sql::escape_ident(column),
8100                    sql
8101                ));
8102                continue;
8103            }
8104            // A computed field is a column as far as ordering is concerned:
8105            // the row is the argument, and the call is what is sorted on.
8106            let function = names
8107                .computed_source(&table.schema, &table.name, key)
8108                .unwrap_or(key);
8109            if let Some(definition) = table.get_computed_column(function) {
8110                terms.push(format!(
8111                    "{} {}",
8112                    computed_call(definition, &format!("{}.*", reference), reference),
8113                    sql
8114                ));
8115                continue;
8116            }
8117            return Err(async_graphql::Error::new(format!(
8118                "cannot order by unknown column \"{}\" on \"{}\"",
8119                key, table.name
8120            )));
8121        }
8122
8123        // An aggregate of the rows that point here.
8124        if let Some(rel) = key
8125            .strip_suffix("_aggregate")
8126            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list))
8127        {
8128            aggregate_order_terms(value, rel, cache, reference, alias_counter, terms)?;
8129            continue;
8130        }
8131
8132        // A column of the row this one points at.
8133        if let Some(rel) = available.iter().find(|r| r.name == *key && !r.is_list) {
8134            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8135                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8136            if plan.columns.is_empty() {
8137                return Err(async_graphql::Error::new(format!(
8138                    "cannot order by \"{}\": it is not reached by a key",
8139                    key
8140                )));
8141            }
8142            *alias_counter += 1;
8143            let alias = format!("pgrst_ord_{}", alias_counter);
8144            let quoted_alias = postrust_sql::escape_ident(&alias);
8145            let correlation = plan
8146                .columns
8147                .iter()
8148                .map(|(local, foreign)| {
8149                    format!(
8150                        "{} = {}.{}",
8151                        format_args!("{}.{}", reference, postrust_sql::escape_ident(local)),
8152                        quoted_alias,
8153                        postrust_sql::escape_ident(foreign)
8154                    )
8155                })
8156                .collect::<Vec<_>>()
8157                .join(" AND ");
8158
8159            let target_qi = postrust_core::api_request::QualifiedIdentifier::new(
8160                &plan.foreign_schema,
8161                &plan.foreign_table,
8162            );
8163            let Some(target) = cache.get_table(&target_qi) else {
8164                return Err(async_graphql::Error::new(format!(
8165                    "unknown table \"{}\"",
8166                    plan.foreign_table
8167                )));
8168            };
8169
8170            // The related row's own terms, then wrapped one at a time: a
8171            // subquery yields one value, so each term is its own subselect.
8172            let mut nested = Vec::new();
8173            order_terms_into(
8174                value,
8175                cache,
8176                relationships,
8177                &rel.target_type,
8178                target,
8179                &quoted_alias,
8180                names,
8181                alias_counter,
8182                &mut nested,
8183            )?;
8184            for term in nested {
8185                let (expression, direction) = term
8186                    .rsplit_once(' ')
8187                    .map(|(e, d)| (e.to_string(), d.to_string()))
8188                    .unwrap_or((term.clone(), String::new()));
8189                terms.push(format!(
8190                    "(SELECT {} FROM {}.{} AS {} WHERE {}) {}",
8191                    expression,
8192                    postrust_sql::escape_ident(&plan.foreign_schema),
8193                    postrust_sql::escape_ident(&plan.foreign_table),
8194                    quoted_alias,
8195                    correlation,
8196                    direction
8197                ));
8198            }
8199            continue;
8200        }
8201
8202        return Err(async_graphql::Error::new(format!(
8203            "cannot order by \"{}\" on \"{}\"",
8204            key, table.name
8205        )));
8206    }
8207    Ok(())
8208}
8209
8210/// The `ORDER BY` terms for an aggregate of a row's children.
8211fn aggregate_order_terms(
8212    value: &serde_json::Value,
8213    rel: &RelationshipField,
8214    cache: &SchemaCache,
8215    reference: &str,
8216    alias_counter: &mut usize,
8217    terms: &mut Vec<String>,
8218) -> Result<(), async_graphql::Error> {
8219    let serde_json::Value::Object(spec) = value else {
8220        return Err(async_graphql::Error::new(
8221            "ordering by an aggregate takes an object, such as {count: desc}",
8222        ));
8223    };
8224    let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8225        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8226    if plan.columns.is_empty() && plan.function.is_none() {
8227        return Err(async_graphql::Error::new(format!(
8228            "cannot order by an aggregate of \"{}\": it is not reached by a key",
8229            rel.name
8230        )));
8231    }
8232    // A computed relationship whose function takes more than the row cannot be
8233    // ordered by here: there is nowhere in `order_by` to write the arguments.
8234    if plan.function.is_some() && !rel.arguments.is_empty() {
8235        return Err(async_graphql::Error::new(format!(
8236            "cannot order by an aggregate of \"{}\": it takes arguments, and an \
8237             ordering has nowhere to write them",
8238            rel.name
8239        )));
8240    }
8241
8242    *alias_counter += 1;
8243    let alias = postrust_sql::escape_ident(&format!("pgrst_ord_{}", alias_counter));
8244    // A computed relationship is correlated by argument -- the function takes
8245    // the parent row -- where a key relationship is correlated by columns.
8246    let (source, correlation) = match &plan.function {
8247        Some(function) => {
8248            let session = plan.row_argument.as_ref().and_then(|row_argument| {
8249                computed_session_argument(cache, function, row_argument)
8250                    .map(|session| (row_argument.clone(), session))
8251            });
8252            let call = match session {
8253                Some((row_argument, session)) => format!(
8254                    "{} => {}, {} => {}",
8255                    postrust_sql::escape_ident(&row_argument),
8256                    reference,
8257                    postrust_sql::escape_ident(&session),
8258                    SESSION_ARGUMENT
8259                ),
8260                None => reference.to_string(),
8261            };
8262            (
8263                format!(
8264                    "{}.{}({})",
8265                    postrust_sql::escape_ident(&function.schema),
8266                    postrust_sql::escape_ident(&function.name),
8267                    call
8268                ),
8269                "true".to_string(),
8270            )
8271        }
8272        None => (
8273            format!(
8274                "{}.{}",
8275                postrust_sql::escape_ident(&plan.foreign_schema),
8276                postrust_sql::escape_ident(&plan.foreign_table)
8277            ),
8278            plan.columns
8279                .iter()
8280                .map(|(local, foreign)| {
8281                    format!(
8282                        "{}.{} = {}.{}",
8283                        reference,
8284                        postrust_sql::escape_ident(local),
8285                        alias,
8286                        postrust_sql::escape_ident(foreign)
8287                    )
8288                })
8289                .collect::<Vec<_>>()
8290                .join(" AND "),
8291        ),
8292    };
8293
8294    // `{count: desc}` is one term; `{max: {id: desc}}` is one per column.
8295    let mut wanted: Vec<(String, String)> = Vec::new();
8296    for (function, argument) in spec {
8297        // A direction given as null is no direction, as everywhere else an
8298        // ordering is read.
8299        if argument.is_null() {
8300            continue;
8301        }
8302        if function == "count" {
8303            let name = argument.as_str().unwrap_or_default();
8304            let Some(direction) = crate::input::order_by::direction_sql(name) else {
8305                return Err(async_graphql::Error::new(format!(
8306                    "\"{}\" is not a sort direction",
8307                    name
8308                )));
8309            };
8310            wanted.push(("count(*)".to_string(), direction.to_string()));
8311            continue;
8312        }
8313        let serde_json::Value::Object(columns) = argument else {
8314            continue;
8315        };
8316        for (column, direction) in columns {
8317            if direction.is_null() {
8318                continue;
8319            }
8320            let name = direction.as_str().unwrap_or_default();
8321            let Some(direction) = crate::input::order_by::direction_sql(name) else {
8322                return Err(async_graphql::Error::new(format!(
8323                    "\"{}\" is not a sort direction",
8324                    name
8325                )));
8326            };
8327            // The function comes from the generated input, not from the
8328            // request: a client can only name one that was offered.
8329            wanted.push((
8330                format!(
8331                    "{}({}.{})",
8332                    function,
8333                    alias,
8334                    postrust_sql::escape_ident(column)
8335                ),
8336                direction.to_string(),
8337            ));
8338        }
8339    }
8340
8341    for (expression, direction) in wanted {
8342        terms.push(format!(
8343            "(SELECT {} FROM {} AS {} WHERE {}) {}",
8344            expression, source, alias, correlation, direction
8345        ));
8346    }
8347    Ok(())
8348}
8349
8350/// The `DISTINCT ON (...)` prefix, and the ordering that has to go with it.
8351///
8352/// PostgreSQL keeps the first row of each DISTINCT ON group in the query's own
8353/// order, and picks arbitrarily where the ordering does not begin with the
8354/// distinct columns -- so which row survives would depend on the plan. Hasura
8355/// refuses that query rather than answering it, and so does this: prepending
8356/// the distinct columns instead produced an answer, and a wrong one, since
8357/// `ORDER BY "department", "department" DESC` is decided by its first term and
8358/// sorts ascending. Where nothing was ordered at all there is nothing to
8359/// disagree with, so the distinct columns become the ordering.
8360///
8361/// `written` is the ordering as its comma-separated terms, without the
8362/// keyword; the answer is in the same spelling.
8363fn distinct_on_clause(
8364    distinct_on: &[String],
8365    written: Option<&str>,
8366) -> Result<(String, Option<String>), async_graphql::Error> {
8367    if distinct_on.is_empty() {
8368        return Ok((String::new(), written.map(str::to_string)));
8369    }
8370    let terms: Vec<&str> = written
8371        .map(|written| written.split(", ").collect())
8372        .unwrap_or_default();
8373    // Terms are qualified and the distinct columns may not be, so they are
8374    // compared by the name itself.
8375    let column_of = |term: &str| {
8376        let expression = term.split_whitespace().next().unwrap_or(term);
8377        expression
8378            .rsplit_once('.')
8379            .map(|(_, name)| name)
8380            .unwrap_or(expression)
8381            .to_string()
8382    };
8383    let leading: Vec<String> = terms
8384        .iter()
8385        .take(distinct_on.len())
8386        .map(|term| column_of(term))
8387        .collect();
8388    if !terms.is_empty()
8389        && (leading.len() != distinct_on.len()
8390            || distinct_on
8391                .iter()
8392                .any(|column| !leading.contains(&column_of(column))))
8393    {
8394        return Err(validation_error(
8395            "\"distinct_on\" columns must match initial \"order_by\" columns",
8396        ));
8397    }
8398    let order = match terms.is_empty() {
8399        true => Some(distinct_on.join(", ")),
8400        false => written.map(str::to_string),
8401    };
8402    Ok((format!("DISTINCT ON ({}) ", distinct_on.join(", ")), order))
8403}
8404
8405/// Build the `DISTINCT ON (...)` prefix from the `distinct_on` argument.
8406///
8407/// PostgreSQL requires the leftmost `ORDER BY` terms to match the distinct
8408/// columns, and picks an arbitrary surviving row otherwise. The caller
8409/// prepends these terms to whatever ordering was asked for rather than
8410/// replacing it, so `distinct_on: [name]` with `order_by: [{id: desc}]` keeps
8411/// the highest id per name instead of an unpredictable one.
8412async fn build_distinct_on(
8413    ctx: &ResolverContext<'_>,
8414    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8415    schema_name: &str,
8416    table_name: &str,
8417    names: &crate::names::NameOverrides,
8418) -> Result<Vec<String>, async_graphql::Error> {
8419    let Ok(arg) = ctx.args.try_get("distinct_on") else {
8420        return Ok(Vec::new());
8421    };
8422    let value = accessor_to_json(&arg);
8423    let requested: Vec<String> = match &value {
8424        serde_json::Value::Array(items) => items
8425            .iter()
8426            .filter_map(|i| i.as_str().map(|s| s.to_string()))
8427            .collect(),
8428        serde_json::Value::String(one) => vec![one.clone()],
8429        _ => Vec::new(),
8430    };
8431    if requested.is_empty() {
8432        return Ok(Vec::new());
8433    }
8434
8435    let guard = schema_cache
8436        .get()
8437        .await
8438        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8439    let cache = guard
8440        .as_ref()
8441        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8442    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8443    let table = cache
8444        .get_table(&qi)
8445        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8446
8447    let mut quoted = Vec::with_capacity(requested.len());
8448    for field in requested {
8449        let name = table_column_for(names, table, &field).to_string();
8450        if table.get_column(&name).is_none() {
8451            return Err(async_graphql::Error::new(format!(
8452                "cannot take distinct on unknown column \"{}\" of \"{}\"",
8453                field, table_name
8454            )));
8455        }
8456        quoted.push(postrust_sql::escape_ident(&name));
8457    }
8458    Ok(quoted)
8459}
8460
8461/// Build the SELECT-list expressions that embed relationships in one query.
8462///
8463/// The GraphQL mirror of the REST builder: each requested relationship becomes a
8464/// correlated subselect yielding JSON, so the whole selection comes back from
8465/// the parent query instead of one query per relationship per level.
8466/// The SELECT-list entries for any computed columns the selection asks for.
8467///
8468/// A computed column is a function of one argument of the table's own row
8469/// type, so it is not in `*` and has to be named. PostgreSQL's functional
8470/// notation reads `upper_name(author.*)` and `author.upper_name` as the same
8471/// call; the explicit form is written here because it says which function is
8472/// being called.
8473#[allow(clippy::too_many_arguments)] // the fields, their source, and the binding state
8474fn computed_projections<'a>(
8475    table: &postrust_core::schema_cache::Table,
8476    fields: impl IntoIterator<Item = SelectionField<'a>>,
8477    row_reference: &str,
8478    names: &crate::names::NameOverrides,
8479    schema_cache: &SchemaCache,
8480    param_idx: &mut usize,
8481    values: &mut Vec<serde_json::Value>,
8482) -> Result<Vec<String>, async_graphql::Error> {
8483    let mut projections = Vec::new();
8484    for field in fields {
8485        let name = field.name();
8486        // A real column wins, and the projection already carries it -- under
8487        // this name, whether or not that is the column's own.
8488        if table
8489            .get_column(table_column_for(names, table, name))
8490            .is_some()
8491        {
8492            continue;
8493        }
8494        // The field may be exposed under a name that was given rather than
8495        // the function's own, so the call is looked up from either side.
8496        let function = names
8497            .computed_source(&table.schema, &table.name, name)
8498            .unwrap_or(name);
8499        let Some(computed) = table.get_computed_column(function) else {
8500            continue;
8501        };
8502        let arguments = crate::schema::computed_caller_arguments(computed, schema_cache);
8503        let given = match arguments.is_empty() {
8504            true => None,
8505            false => embed_arguments(field).remove("args"),
8506        };
8507        projections.push(format!(
8508            "{} AS {}",
8509            computed_column_call(
8510                computed,
8511                row_reference,
8512                name,
8513                &arguments,
8514                given.as_ref(),
8515                param_idx,
8516                values,
8517            )?,
8518            postrust_sql::escape_ident(name)
8519        ));
8520    }
8521    Ok(projections)
8522}
8523
8524/// The call behind a computed field, including whatever the caller passed.
8525///
8526/// The ordinary case is a function of the row alone, which is written
8527/// positionally: that is the call REST writes, and the one a client that never
8528/// heard of `args` is asking for. Anything else has to be written by name --
8529/// the row is then not the only parameter and may not be the first -- so the
8530/// row, the session where the function asks for one, and each argument the
8531/// caller gave are all named.
8532fn computed_column_call(
8533    computed: &postrust_core::schema_cache::ComputedColumn,
8534    row: &str,
8535    field_name: &str,
8536    arguments: &[(String, String, bool)],
8537    given: Option<&serde_json::Value>,
8538    param_idx: &mut usize,
8539    values: &mut Vec<serde_json::Value>,
8540) -> Result<String, async_graphql::Error> {
8541    if arguments.is_empty() {
8542        return Ok(computed_call(computed, row, row));
8543    }
8544    let Some(row_argument) = &computed.row_argument else {
8545        return Ok(computed_call(computed, row, row));
8546    };
8547    let function = format!(
8548        "{}.{}",
8549        postrust_sql::escape_ident(&computed.function.schema),
8550        postrust_sql::escape_ident(&computed.function.name)
8551    );
8552    let mut passed = vec![format!(
8553        "{} => {}",
8554        postrust_sql::escape_ident(row_argument),
8555        row
8556    )];
8557    if let Some(session) = &computed.session_argument {
8558        passed.push(format!(
8559            "{} => {}",
8560            postrust_sql::escape_ident(session),
8561            SESSION_ARGUMENT
8562        ));
8563    }
8564    for (name, pg_type, required) in arguments {
8565        match given.and_then(|given| given.get(name)) {
8566            None if *required => {
8567                return Err(async_graphql::Error::new(format!(
8568                    "{} needs the argument \"{}\"",
8569                    field_name, name
8570                )))
8571            }
8572            // Left out is left out, so the function's own default applies.
8573            None => continue,
8574            Some(value) => {
8575                passed.push(format!(
8576                    "{} => ${}::{}",
8577                    postrust_sql::escape_ident(name),
8578                    param_idx,
8579                    pg_type
8580                ));
8581                *param_idx += 1;
8582                values.push(value.clone());
8583            }
8584        }
8585    }
8586    Ok(format!("{}({})", function, passed.join(", ")))
8587}
8588
8589/// The `count(...)` a selection asked for.
8590///
8591/// `count` alone is `count(*)`, which counts rows. `count(columns: [a])` is
8592/// `count("a")`, which counts the rows where `a` is not null -- a different
8593/// number, and the one the client asked for. `distinct: true` counts the
8594/// distinct values among them, and several columns are counted as the row
8595/// they make, which is how PostgreSQL counts more than one thing at once.
8596///
8597/// The column names come from the generated enum, so a selection can only
8598/// name a column the table has; what it may not know is the column's own name
8599/// where the schema exposes it under another.
8600fn count_expression(
8601    field: async_graphql::SelectionField<'_>,
8602    table: (&str, &str),
8603    alias: Option<&str>,
8604    names: &crate::names::NameOverrides,
8605) -> String {
8606    let arguments = embed_arguments(field);
8607    let requested = match arguments.get("columns") {
8608        Some(serde_json::Value::Array(items)) => items
8609            .iter()
8610            .filter_map(|item| item.as_str())
8611            .collect::<Vec<_>>(),
8612        Some(serde_json::Value::String(one)) => vec![one.as_str()],
8613        _ => Vec::new(),
8614    };
8615    if requested.is_empty() {
8616        return "count(*)".to_string();
8617    }
8618    let columns: Vec<String> = requested
8619        .into_iter()
8620        .map(|column| {
8621            let (schema, name) = table;
8622            let source = column_for(names, schema, name, column);
8623            match alias {
8624                Some(alias) => format!(
8625                    "{}.{}",
8626                    postrust_sql::escape_ident(alias),
8627                    postrust_sql::escape_ident(source)
8628                ),
8629                None => postrust_sql::escape_ident(source),
8630            }
8631        })
8632        .collect();
8633    // One column counts itself; several count the row they make, since
8634    // `count(a, b)` is not a call PostgreSQL has.
8635    let counted = match columns.len() {
8636        1 => columns.into_iter().next().unwrap_or_default(),
8637        _ => format!("({})", columns.join(", ")),
8638    };
8639    let distinct = matches!(
8640        arguments.get("distinct"),
8641        Some(serde_json::Value::Bool(true))
8642    );
8643    match distinct {
8644        true => format!("count(DISTINCT {})", counted),
8645        false => format!("count({})", counted),
8646    }
8647}
8648
8649/// The SELECT list for a nested aggregate.
8650///
8651/// `articles_aggregate { aggregate { count } nodes { title } }` becomes one
8652/// row per parent, correlated the way any embed is. Both halves are aggregates
8653/// over the same correlated set, so they go in one select list rather than two
8654/// queries -- `count(*)` and `json_agg(...)` read the same rows.
8655#[allow(clippy::too_many_arguments)] // the recursion carries its whole context
8656fn nested_aggregate_select(
8657    caller: &crate::role::Caller<'_>,
8658    selection: async_graphql::SelectionField<'_>,
8659    child_alias: &str,
8660    schema_cache: &SchemaCache,
8661    relationships: &HashMap<String, Vec<RelationshipField>>,
8662    target_type: &str,
8663    child_table: Option<&postrust_core::schema_cache::Table>,
8664    max_rows: Option<i64>,
8665    alias_counter: &mut usize,
8666    param_idx: &mut usize,
8667    values: &mut Vec<serde_json::Value>,
8668    names: &crate::names::NameOverrides,
8669) -> Result<String, async_graphql::Error> {
8670    use crate::schema::aggregate as agg;
8671
8672    let mut parts: Vec<String> = Vec::new();
8673
8674    for child in selection.selection_set() {
8675        match child.name() {
8676            "aggregate" => {
8677                let of_table = child_table
8678                    .map(|table| (table.schema.as_str(), table.name.as_str()))
8679                    .unwrap_or(("", ""));
8680                // Each `count` is answered under the name it was asked for,
8681                // since two of them may count different things. Every other
8682                // aggregate is one object read under however many names, so
8683                // its columns are the union of what all of them named.
8684                let mut counts: Vec<(String, String)> = Vec::new();
8685                let mut wanted: BTreeMap<&str, Vec<String>> = BTreeMap::new();
8686                for function in child.selection_set() {
8687                    if function.name() == "count" {
8688                        counts.push((
8689                            function.alias().unwrap_or("count").to_string(),
8690                            count_expression(function, of_table, Some(child_alias), names),
8691                        ));
8692                        continue;
8693                    }
8694                    let sql_function = agg::NUMERIC_AGGREGATES
8695                        .iter()
8696                        .chain(agg::ORDERED_AGGREGATES.iter())
8697                        .find(|(name, _)| *name == function.name())
8698                        .map(|(name, _)| *name);
8699                    let Some(sql_function) = sql_function else {
8700                        continue;
8701                    };
8702                    let columns = wanted.entry(sql_function).or_default();
8703                    for column in function.selection_set() {
8704                        let name = column.name().to_string();
8705                        if !columns.contains(&name) {
8706                            columns.push(name);
8707                        }
8708                    }
8709                }
8710                // Answered whether or not it was asked for, for the reason
8711                // the root aggregate answers it.
8712                let mut fields = match counts.iter().any(|(key, _)| key == "count") {
8713                    true => Vec::new(),
8714                    false => vec!["'count', count(*)".to_string()],
8715                };
8716                for (key, expression) in &counts {
8717                    fields.push(format!("'{}', {}", key.replace('\'', "''"), expression));
8718                }
8719                for (sql_function, named) in &wanted {
8720                    let columns: Vec<String> = named
8721                        .iter()
8722                        .map(|column| {
8723                            let source = child_table
8724                                .map(|table| table_column_for(names, table, column))
8725                                .unwrap_or(column.as_str());
8726                            format!(
8727                                "'{}', {}({}.{})",
8728                                column.replace('\'', "''"),
8729                                sql_function,
8730                                postrust_sql::escape_ident(child_alias),
8731                                postrust_sql::escape_ident(source)
8732                            )
8733                        })
8734                        .collect();
8735                    if !columns.is_empty() {
8736                        fields.push(format!(
8737                            "'{}', json_build_object({})",
8738                            sql_function,
8739                            columns.join(", ")
8740                        ));
8741                    }
8742                }
8743                parts.push(format!(
8744                    "json_build_object({}) AS {}",
8745                    fields.join(", "),
8746                    postrust_sql::escape_ident("aggregate")
8747                ));
8748            }
8749            // `nodes` under an aggregate is a rows selection like any other:
8750            // what it names may be a column, a computed field, or a
8751            // relationship of its own, and the last of those is a further
8752            // correlated subselect rather than a column of this one.
8753            "nodes" => {
8754                let embeds = build_embed_expressions(
8755                    caller,
8756                    schema_cache,
8757                    relationships,
8758                    target_type,
8759                    child_alias,
8760                    child.selection_set(),
8761                    max_rows,
8762                    alias_counter,
8763                    param_idx,
8764                    values,
8765                    names,
8766                )?;
8767                let mut columns: Vec<String> = Vec::new();
8768                for column in child.selection_set() {
8769                    let name = column.name();
8770                    if name == "__typename" || embeds.iter().any(|(field, _)| field == name) {
8771                        continue;
8772                    }
8773                    columns.push(format!(
8774                        "'{}', {}",
8775                        name.replace('\'', "''"),
8776                        column_expression(child_table, child_alias, name, names)
8777                    ));
8778                }
8779                for (field, expression) in &embeds {
8780                    columns.push(format!("'{}', {}", field.replace('\'', "''"), expression));
8781                }
8782                let row = if columns.is_empty() {
8783                    format!("row_to_json({})", postrust_sql::escape_ident(child_alias))
8784                } else {
8785                    format!("json_build_object({})", columns.join(", "))
8786                };
8787                parts.push(format!(
8788                    "COALESCE(json_agg({}), '[]'::json) AS {}",
8789                    row,
8790                    postrust_sql::escape_ident("nodes")
8791                ));
8792            }
8793            _ => {}
8794        }
8795    }
8796
8797    if parts.is_empty() {
8798        // A selection of nothing but `__typename`. One row, no columns, is not
8799        // valid SQL; a count nobody reads is.
8800        parts.push("count(*) AS pgrst_empty".to_string());
8801    }
8802    Ok(parts.join(", "))
8803}
8804
8805/// How one named field of a row is read, given the alias the row goes by.
8806///
8807/// A column is itself; a computed field is the call that produces it. Which of
8808/// the two a name is depends on the table, so this is the one place that asks.
8809fn column_expression(
8810    table: Option<&postrust_core::schema_cache::Table>,
8811    alias: &str,
8812    name: &str,
8813    names: &crate::names::NameOverrides,
8814) -> String {
8815    let column = table
8816        .map(|t| table_column_for(names, t, name))
8817        .unwrap_or(name);
8818    let plain = format!(
8819        "{}.{}",
8820        postrust_sql::escape_ident(alias),
8821        postrust_sql::escape_ident(column)
8822    );
8823    let computed = table
8824        .filter(|t| t.get_column(column).is_none())
8825        .and_then(|t| {
8826            let function = names
8827                .computed_source(&t.schema, &t.name, name)
8828                .unwrap_or(name);
8829            t.get_computed_column(function)
8830        });
8831    match computed {
8832        Some(definition) => computed_call(
8833            definition,
8834            &format!("{}.*", postrust_sql::escape_ident(alias)),
8835            &postrust_sql::escape_ident(alias),
8836        ),
8837        None => plain,
8838    }
8839}
8840
8841/// Render `order_by` terms against a table, qualified with an alias.
8842///
8843/// The root field's ordering reads its argument from the resolver context and
8844/// needs the schema cache asynchronously; an embed already holds both, so this
8845/// takes the value directly. Columns are checked against the table before they
8846/// are quoted, so an unknown or crafted name is refused rather than
8847/// interpolated.
8848fn order_terms(
8849    order: &serde_json::Value,
8850    schema_cache: &SchemaCache,
8851    schema_name: &str,
8852    table_name: &str,
8853    alias: &str,
8854    names: &crate::names::NameOverrides,
8855) -> Result<Option<String>, async_graphql::Error> {
8856    let entries: Vec<&serde_json::Value> = match order {
8857        serde_json::Value::Array(items) => items.iter().collect(),
8858        serde_json::Value::Object(_) => vec![order],
8859        _ => return Ok(None),
8860    };
8861
8862    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8863    let table = schema_cache
8864        .get_table(&qi)
8865        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8866
8867    let mut terms = Vec::new();
8868    for entry in entries {
8869        let serde_json::Value::Object(map) = entry else {
8870            continue;
8871        };
8872        for (field, direction) in map {
8873            // A direction given as null is no direction, as at the root.
8874            if direction.is_null() {
8875                continue;
8876            }
8877            let column = table_column_for(names, table, field);
8878            if table.get_column(column).is_none() {
8879                return Err(async_graphql::Error::new(format!(
8880                    "cannot order by unknown column \"{}\" on \"{}\"",
8881                    field, table_name
8882                )));
8883            }
8884            let name = direction.as_str().unwrap_or_default();
8885            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8886                async_graphql::Error::new(format!("\"{}\" is not a sort direction", name))
8887            })?;
8888            terms.push(format!(
8889                "{}.{} {}",
8890                postrust_sql::escape_ident(alias),
8891                postrust_sql::escape_ident(column),
8892                sql
8893            ));
8894        }
8895    }
8896
8897    Ok(if terms.is_empty() {
8898        None
8899    } else {
8900        Some(terms.join(", "))
8901    })
8902}
8903
8904/// Whether a to-one relationship always has a row at the other end.
8905///
8906/// It does when this row's own key columns cannot be null: a foreign key
8907/// guarantees a matching row for every value, so a `NOT NULL` key is a
8908/// relationship that cannot be absent, and the field is non-null. A client
8909/// generating types then gets `author` rather than `author | null`, which is
8910/// what the schema actually promises.
8911///
8912/// Only where a foreign key says so. A computed relationship is a function
8913/// call, and a one-to-one reached through the *child's* key is a row that may
8914/// simply not exist.
8915fn always_present(rel: &RelationshipField, table: &postrust_core::schema_cache::Table) -> bool {
8916    use postrust_core::schema_cache::{Cardinality, Relationship};
8917    let Relationship::ForeignKey { cardinality, .. } = &rel.relationship else {
8918        return false;
8919    };
8920    match cardinality {
8921        Cardinality::M2O { .. } => {}
8922        Cardinality::O2O {
8923            is_parent: false, ..
8924        } => {}
8925        _ => return false,
8926    }
8927    let columns = cardinality.columns();
8928    !columns.is_empty()
8929        && columns.iter().all(|(local, _)| {
8930            table
8931                .get_column(local)
8932                .is_some_and(|column| !column.nullable)
8933        })
8934}
8935
8936/// Whether the row at the other end of a relationship carries the key.
8937///
8938/// If it does, this row is written first and its key is pushed down; if this
8939/// row's own column carries it, the other row is written first and its key is
8940/// read back. Cardinality alone does not answer it: a one-to-one is one row in
8941/// either direction, and which side holds the foreign key is the whole
8942/// difference between the two orders.
8943fn child_holds_the_key(relationship: &postrust_core::schema_cache::Relationship) -> bool {
8944    use postrust_core::schema_cache::{Cardinality, Relationship};
8945    match relationship {
8946        Relationship::ForeignKey { cardinality, .. } => match cardinality {
8947            Cardinality::M2O { .. } => false,
8948            Cardinality::O2O { is_parent, .. } => *is_parent,
8949            _ => true,
8950        },
8951        Relationship::Computed { .. } => true,
8952    }
8953}
8954
8955/// The name of a function's session parameter, if it has one.
8956fn computed_session_argument(
8957    cache: &SchemaCache,
8958    function: &postrust_core::api_request::QualifiedIdentifier,
8959    row_argument: &str,
8960) -> Option<String> {
8961    let _ = row_argument;
8962    cache
8963        .routines
8964        .get(function)
8965        .into_iter()
8966        .flatten()
8967        .find(|routine| routine.name == function.name)?
8968        .params
8969        .iter()
8970        .find(|param| {
8971            param.name == "hasura_session" && matches!(param.param_type.as_str(), "json" | "jsonb")
8972        })
8973        .map(|param| param.name.clone())
8974}
8975
8976/// The name of the input holding a computed relationship's own arguments.
8977fn computed_args_type_name(type_name: &str, field: &str) -> String {
8978    format!("{}_{}_args", type_name, field)
8979}
8980
8981/// The argument list a computed relationship's function is called with.
8982///
8983/// `None` where the parent row is the only thing it takes -- the ordinary case,
8984/// and the positional call the REST surface writes. Anything else is written by
8985/// name: the row, the session where the function asks for one, and whatever the
8986/// client put under `args`.
8987fn computed_arguments(
8988    relationship: &RelationshipField,
8989    plan: &postrust_core::embed::EmbedPlan,
8990    parent_row: &str,
8991    given: Option<&serde_json::Value>,
8992    param_idx: &mut usize,
8993    values: &mut Vec<serde_json::Value>,
8994    schema_cache: &SchemaCache,
8995) -> Result<Option<String>, async_graphql::Error> {
8996    let (Some(function), Some(row_argument)) = (&plan.function, &plan.row_argument) else {
8997        return Ok(None);
8998    };
8999    let session = schema_cache
9000        .routines
9001        .get(function)
9002        .into_iter()
9003        .flatten()
9004        .find(|routine| routine.name == function.name)
9005        .and_then(|routine| {
9006            routine
9007                .params
9008                .iter()
9009                .find(|param| {
9010                    param.name == "hasura_session"
9011                        && matches!(param.param_type.as_str(), "json" | "jsonb")
9012                })
9013                .map(|param| param.name.clone())
9014        });
9015    if relationship.arguments.is_empty() && session.is_none() {
9016        return Ok(None);
9017    }
9018
9019    let mut passed = vec![format!(
9020        "{} => {}",
9021        postrust_sql::escape_ident(row_argument),
9022        parent_row
9023    )];
9024    if let Some(session) = session {
9025        passed.push(format!(
9026            "{} => {}",
9027            postrust_sql::escape_ident(&session),
9028            SESSION_ARGUMENT
9029        ));
9030    }
9031    for (name, pg_type, required) in &relationship.arguments {
9032        match given.and_then(|given| given.get(name)) {
9033            None if *required => {
9034                return Err(async_graphql::Error::new(format!(
9035                    "{} needs the argument \"{}\"",
9036                    relationship.name, name
9037                )))
9038            }
9039            // Left out is left out, so the function's own default applies.
9040            None => continue,
9041            Some(value) => {
9042                passed.push(format!(
9043                    "{} => ${}::{}",
9044                    postrust_sql::escape_ident(name),
9045                    param_idx,
9046                    pg_type
9047                ));
9048                *param_idx += 1;
9049                values.push(value.clone());
9050            }
9051        }
9052    }
9053    Ok(Some(passed.join(", ")))
9054}
9055
9056/// The arguments written on an embedded field, as JSON.
9057fn embed_arguments(field: async_graphql::SelectionField<'_>) -> HashMap<String, serde_json::Value> {
9058    field
9059        .arguments()
9060        .map(|args| {
9061            args.into_iter()
9062                .map(|(name, value)| {
9063                    (
9064                        name.to_string(),
9065                        plain_numbers(value.into_json().unwrap_or(serde_json::Value::Null)),
9066                    )
9067                })
9068                .collect()
9069        })
9070        .unwrap_or_default()
9071}
9072
9073/// What an embed's arguments narrow its child rows to: a `DISTINCT ON`
9074/// prefix, a predicate, an ordering, a limit and an offset, in the order the
9075/// SQL takes them.
9076type Narrowing = (String, Option<String>, Option<String>, Option<i64>, i64);
9077
9078/// What an embed's arguments narrow its child rows to.
9079///
9080/// A list and an aggregate of that same list take the same four arguments and
9081/// mean the same thing by them; only what is done with the surviving rows
9082/// differs, so the reading of the arguments is shared.
9083#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
9084fn embed_narrowing(
9085    arguments: &HashMap<String, serde_json::Value>,
9086    plan: &postrust_core::embed::EmbedPlan,
9087    schema_cache: &SchemaCache,
9088    relationships: &HashMap<String, Vec<RelationshipField>>,
9089    target_type: &str,
9090    child_alias: &str,
9091    max_rows: Option<i64>,
9092    param_idx: &mut usize,
9093    values: &mut Vec<serde_json::Value>,
9094    names: &crate::names::NameOverrides,
9095    caller: &crate::role::Caller<'_>,
9096) -> Result<Narrowing, async_graphql::Error> {
9097    // The child's own permission, which is the whole reason an embed is a
9098    // place this has to be applied: `author { articles { ... } }` reads two
9099    // tables, and the rule on the second is not the rule on the first.
9100    let permission =
9101        permission_predicate(caller, names, &plan.foreign_schema, &plan.foreign_table)?;
9102    let requested = arguments.get("where").filter(|v| !v.is_null()).cloned();
9103    let child_where = match and_predicates(requested, permission) {
9104        Some(expression) => {
9105            let child_scope = WhereScope::for_alias(
9106                &plan.foreign_schema,
9107                &plan.foreign_table,
9108                child_alias,
9109                target_type,
9110                schema_cache,
9111                relationships,
9112                names,
9113            )
9114            .for_caller(*caller);
9115            let mut nested_alias = 0usize;
9116            build_condition(
9117                &expression,
9118                &child_scope,
9119                param_idx,
9120                values,
9121                &mut nested_alias,
9122            )?
9123        }
9124        None => None,
9125    };
9126
9127    let child_order = match arguments.get("order_by") {
9128        Some(order) if !order.is_null() => order_terms(
9129            order,
9130            schema_cache,
9131            &plan.foreign_schema,
9132            &plan.foreign_table,
9133            child_alias,
9134            names,
9135        )?,
9136        _ => None,
9137    };
9138
9139    // A limit written on the embed is what the client asked for; the
9140    // configured ceiling still applies as an upper bound, the same way it
9141    // does at the top level -- and so does the child's own permission, which
9142    // bounds rows per parent here rather than per query.
9143    let ceiling = match (
9144        max_rows,
9145        crate::role::read_limit(caller, names, &plan.foreign_schema, &plan.foreign_table),
9146    ) {
9147        (Some(configured), Some(granted)) => Some(configured.min(granted)),
9148        (only, None) | (None, only) => only,
9149    };
9150    let child_limit = match arguments.get("limit").and_then(|v| v.as_i64()) {
9151        Some(requested) => match ceiling {
9152            Some(ceiling) => Some(requested.min(ceiling)),
9153            None => Some(requested),
9154        },
9155        None => ceiling,
9156    };
9157    let child_offset = arguments
9158        .get("offset")
9159        .and_then(|v| v.as_i64())
9160        .unwrap_or(0);
9161
9162    // `author { articles(distinct_on: [title], order_by: {title: asc}) }` --
9163    // one article per title, the same question the root field answers and
9164    // under the same rule about what may be ordered by.
9165    let requested = match arguments.get("distinct_on") {
9166        Some(serde_json::Value::Array(items)) => items
9167            .iter()
9168            .filter_map(|item| item.as_str())
9169            .collect::<Vec<_>>(),
9170        Some(serde_json::Value::String(one)) => vec![one.as_str()],
9171        _ => Vec::new(),
9172    };
9173    let mut distinct_on = Vec::with_capacity(requested.len());
9174    if !requested.is_empty() {
9175        let qi = postrust_core::api_request::QualifiedIdentifier::new(
9176            &plan.foreign_schema,
9177            &plan.foreign_table,
9178        );
9179        let table = schema_cache.get_table(&qi).ok_or_else(|| {
9180            async_graphql::Error::new(format!("unknown table \"{}\"", plan.foreign_table))
9181        })?;
9182        for field in requested {
9183            let column = table_column_for(names, table, field).to_string();
9184            if table.get_column(&column).is_none() {
9185                return Err(async_graphql::Error::new(format!(
9186                    "cannot take distinct on unknown column \"{}\" of \"{}\"",
9187                    field, plan.foreign_table
9188                )));
9189            }
9190            distinct_on.push(format!(
9191                "{}.{}",
9192                postrust_sql::escape_ident(child_alias),
9193                postrust_sql::escape_ident(&column)
9194            ));
9195        }
9196    }
9197    let (distinct, child_order) = distinct_on_clause(&distinct_on, child_order.as_deref())?;
9198
9199    Ok((
9200        distinct,
9201        child_where,
9202        child_order,
9203        child_limit,
9204        child_offset,
9205    ))
9206}
9207
9208#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
9209fn build_embed_expressions<'a>(
9210    caller: &crate::role::Caller<'_>,
9211    schema_cache: &SchemaCache,
9212    relationships: &HashMap<String, Vec<RelationshipField>>,
9213    type_name: &str,
9214    parent_alias: &str,
9215    fields: impl IntoIterator<Item = SelectionField<'a>>,
9216    max_rows: Option<i64>,
9217    alias_counter: &mut usize,
9218    param_idx: &mut usize,
9219    values: &mut Vec<serde_json::Value>,
9220    names: &crate::names::NameOverrides,
9221) -> Result<Vec<(String, String)>, async_graphql::Error> {
9222    let Some(available) = relationships.get(type_name) else {
9223        return Ok(Vec::new());
9224    };
9225
9226    let mut expressions = Vec::new();
9227
9228    for field in fields {
9229        // `<relationship>_aggregate` is the same embed with an aggregate
9230        // select list, so it is resolved here rather than by a resolver of its
9231        // own -- the correlation is what makes it a per-parent answer.
9232        let aggregate_of = field
9233            .name()
9234            .strip_suffix("_aggregate")
9235            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list));
9236
9237        if let Some(rel) = aggregate_of {
9238            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9239                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9240            *alias_counter += 1;
9241            let child_alias = format!("a{}", alias_counter);
9242
9243            // The same narrowing an embedded list takes. `where` here decides
9244            // which rows are counted, not which are returned, so it goes on the
9245            // set the aggregate reads rather than on the answer.
9246            let arguments = embed_arguments(field);
9247            let (child_distinct, child_where, child_order, child_limit, child_offset) =
9248                embed_narrowing(
9249                    &arguments,
9250                    &plan,
9251                    schema_cache,
9252                    relationships,
9253                    &rel.target_type,
9254                    &child_alias,
9255                    max_rows,
9256                    param_idx,
9257                    values,
9258                    names,
9259                    caller,
9260                )?;
9261
9262            let child_table =
9263                schema_cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
9264                    &plan.foreign_schema,
9265                    &plan.foreign_table,
9266                ));
9267            let select = nested_aggregate_select(
9268                caller,
9269                field,
9270                &child_alias,
9271                schema_cache,
9272                relationships,
9273                &rel.target_type,
9274                child_table,
9275                max_rows,
9276                alias_counter,
9277                param_idx,
9278                values,
9279                names,
9280            )?;
9281
9282            let call = computed_arguments(
9283                rel,
9284                &plan,
9285                &postrust_sql::escape_ident(parent_alias),
9286                arguments.get("args"),
9287                param_idx,
9288                values,
9289                schema_cache,
9290            )?;
9291            let expression = plan
9292                .aggregate_expression(
9293                    parent_alias,
9294                    &postrust_sql::escape_ident(parent_alias),
9295                    &child_alias,
9296                    &select,
9297                    child_limit,
9298                    child_offset,
9299                    child_where.as_deref(),
9300                    child_order.as_deref(),
9301                    call.as_deref(),
9302                    &child_distinct,
9303                    // A GraphQL body is JSON throughout -- nothing here
9304                    // renders a row as text -- and Hasura keeps the order the
9305                    // fields were selected in.
9306                    postrust_core::EmbedRendering::Verbatim,
9307                )
9308                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9309            expressions.push((field.name().to_string(), expression));
9310            continue;
9311        }
9312
9313        let Some(rel) = available.iter().find(|r| r.name == field.name()) else {
9314            continue;
9315        };
9316
9317        let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9318            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9319
9320        *alias_counter += 1;
9321        let child_alias = format!("e{}", alias_counter);
9322
9323        let nested = build_embed_expressions(
9324            caller,
9325            schema_cache,
9326            relationships,
9327            &rel.target_type,
9328            &child_alias,
9329            field.selection_set(),
9330            max_rows,
9331            alias_counter,
9332            param_idx,
9333            values,
9334            names,
9335        )?;
9336
9337        // The arguments written on the embed itself.
9338        let arguments = embed_arguments(field);
9339        let (child_distinct, child_where, child_order, child_limit, child_offset) =
9340            embed_narrowing(
9341                &arguments,
9342                &plan,
9343                schema_cache,
9344                relationships,
9345                &rel.target_type,
9346                &child_alias,
9347                max_rows,
9348                param_idx,
9349                values,
9350                names,
9351                caller,
9352            )?;
9353
9354        // Leaf fields are columns; anything that resolved to a relationship is
9355        // an expression instead.
9356        let child_relationships = relationships.get(&rel.target_type);
9357        let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
9358            &plan.foreign_schema,
9359            &plan.foreign_table,
9360        );
9361        let child_table = schema_cache.get_table(&child_qi);
9362        let mut parts: Vec<String> = Vec::new();
9363        for sub in field.selection_set() {
9364            let name = sub.name();
9365            let is_relationship = child_relationships
9366                .map(|rels| {
9367                    rels.iter().any(|r| {
9368                        r.name == name || (r.is_list && format!("{}_aggregate", r.name) == name)
9369                    })
9370                })
9371                .unwrap_or(false);
9372            if is_relationship {
9373                continue;
9374            }
9375            // A computed column inside an embed is the same call, against the
9376            // child's alias rather than the table.
9377            let column = child_table
9378                .map(|t| table_column_for(names, t, name))
9379                .unwrap_or(name);
9380            let computed = child_table
9381                .filter(|t| t.get_column(column).is_none())
9382                .and_then(|t| {
9383                    let function = names
9384                        .computed_source(&t.schema, &t.name, name)
9385                        .unwrap_or(name);
9386                    t.get_computed_column(function)
9387                });
9388            match computed {
9389                Some(definition) => parts.push(format!(
9390                    "{} AS {}",
9391                    computed_call(
9392                        definition,
9393                        &format!("{}.*", postrust_sql::escape_ident(&child_alias)),
9394                        &postrust_sql::escape_ident(&child_alias),
9395                    ),
9396                    postrust_sql::escape_ident(name)
9397                )),
9398                // A column the child exposes under another name is selected as
9399                // that name, since the row it lands in is the client's.
9400                None if column != name => parts.push(format!(
9401                    "{} AS {}",
9402                    postrust_sql::escape_ident(column),
9403                    postrust_sql::escape_ident(name)
9404                )),
9405                None => parts.push(postrust_sql::escape_ident(name)),
9406            }
9407        }
9408        if parts.is_empty() && nested.is_empty() {
9409            parts.push(
9410                child_table
9411                    .and_then(|table| rename_projection(table, &child_alias, names))
9412                    .unwrap_or_else(|| format!("{}.*", postrust_sql::escape_ident(&child_alias))),
9413            );
9414        }
9415        for (field_name, expression) in nested {
9416            parts.push(format!(
9417                "{} AS {}",
9418                expression,
9419                postrust_sql::escape_ident(&field_name)
9420            ));
9421        }
9422
9423        let computed_call_arguments = computed_arguments(
9424            rel,
9425            &plan,
9426            &postrust_sql::escape_ident(parent_alias),
9427            arguments.get("args"),
9428            param_idx,
9429            values,
9430            schema_cache,
9431        )?;
9432        let expression = plan
9433            .embed_expression(
9434                parent_alias,
9435                // A computed relationship is correlated by argument rather
9436                // than by a key: the function takes the parent row, and an
9437                // alias names that row.
9438                &postrust_sql::escape_ident(parent_alias),
9439                &child_alias,
9440                &format!("{}{}", child_distinct, parts.join(", ")),
9441                child_limit,
9442                child_offset,
9443                child_where.as_deref(),
9444                child_order.as_deref(),
9445                computed_call_arguments.as_deref(),
9446                postrust_core::EmbedRendering::Verbatim,
9447            )
9448            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9449
9450        expressions.push((rel.name.clone(), expression));
9451    }
9452
9453    Ok(expressions)
9454}
9455
9456/// What embedding a relationship needs, independent of the rows involved.
9457///
9458/// The connection is passed alongside rather than held here: embedding runs on
9459/// the request's transaction, and a shared reference to this struct could not
9460/// hand out the mutable borrow the queries need.
9461struct EmbedContext<'c> {
9462    schema_cache: &'c SchemaCache,
9463    relationships: &'c HashMap<String, Vec<RelationshipField>>,
9464    max_rows: Option<i64>,
9465}
9466
9467/// Embed the relationship fields requested on `rows`.
9468///
9469/// One query per relationship per level, not one per row: the parents' join
9470/// keys are collected and passed as a single array. Recurses so a nested
9471/// selection costs one further query per relationship at each depth.
9472fn embed_relationships<'f>(
9473    conn: &'f mut sqlx::PgConnection,
9474    ctx: &'f EmbedContext<'f>,
9475    type_name: &'f str,
9476    selection: async_graphql::SelectionField<'f>,
9477    rows: &'f mut [serde_json::Value],
9478) -> futures::future::BoxFuture<'f, Result<(), async_graphql::Error>> {
9479    Box::pin(async move {
9480        if rows.is_empty() {
9481            return Ok(());
9482        }
9483
9484        let Some(available) = ctx.relationships.get(type_name) else {
9485            return Ok(());
9486        };
9487
9488        for requested in selection.selection_set() {
9489            let Some(rel) = available.iter().find(|r| r.name == requested.name()) else {
9490                continue;
9491            };
9492
9493            let plan =
9494                postrust_core::embed::EmbedPlan::resolve(&rel.relationship, ctx.schema_cache)
9495                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9496
9497            let keys = postrust_core::embed::parent_keys(rows, &plan.local_column);
9498
9499            // Project only what the selection asked for. A GraphQL selection
9500            // names its leaf fields, so the columns are known before the query
9501            // and an unrequested column need not be read, serialised, sent and
9502            // parsed just to be dropped.
9503            //
9504            // A nested relationship joins on a column of the child row, so that
9505            // column is added even when it was not selected; it is removed again
9506            // when the response is shaped. A selection whose sub-fields cannot
9507            // all be resolved to columns falls back to every column.
9508            let mut child_columns: Vec<String> = Vec::new();
9509            let mut project_everything = false;
9510            let child_relationships = ctx.relationships.get(&rel.target_type);
9511            for field in requested.selection_set() {
9512                let name = field.name();
9513                match child_relationships.and_then(|rels| rels.iter().find(|r| r.name == name)) {
9514                    Some(nested_rel) => {
9515                        match postrust_core::embed::EmbedPlan::resolve(
9516                            &nested_rel.relationship,
9517                            ctx.schema_cache,
9518                        ) {
9519                            Ok(nested_plan) => child_columns.push(nested_plan.local_column),
9520                            Err(_) => project_everything = true,
9521                        }
9522                    }
9523                    None => child_columns.push(name.to_string()),
9524                }
9525            }
9526            if project_everything || child_columns.is_empty() {
9527                child_columns.clear();
9528            }
9529
9530            let mut grouped = if keys.is_empty() {
9531                std::collections::HashMap::new()
9532            } else {
9533                let sql = plan
9534                    .children_grouped_sql(ctx.max_rows, &child_columns)
9535                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9536
9537                let fetched = sqlx::query(&sql)
9538                    .bind(&keys)
9539                    .fetch_all(&mut *conn)
9540                    .await
9541                    .map_err(database_error)?;
9542
9543                // The query returns the join key and a JSON array of that key's
9544                // children, grouped by PostgreSQL rather than row by row here.
9545                use sqlx::Row;
9546                let pairs: Vec<(serde_json::Value, serde_json::Value)> = fetched
9547                    .into_iter()
9548                    .filter_map(|row| {
9549                        Some((
9550                            row.try_get::<serde_json::Value, _>(0).ok()?,
9551                            row.try_get::<serde_json::Value, _>(1).ok()?,
9552                        ))
9553                    })
9554                    .collect();
9555
9556                postrust_core::embed::group_from_aggregated(pairs)
9557            };
9558
9559            // Recurse before attaching, so nested embeds land in the values
9560            // copied onto the parents. One query serves every child row at this
9561            // level, so the rows are flattened for the call and put back
9562            // afterwards; skipped when the selection asks for nothing deeper.
9563            let has_deeper_embed = ctx
9564                .relationships
9565                .get(&rel.target_type)
9566                .map(|rels| {
9567                    requested
9568                        .selection_set()
9569                        .any(|field| rels.iter().any(|r| r.name == field.name()))
9570                })
9571                .unwrap_or(false);
9572
9573            if has_deeper_embed {
9574                let mut order: Vec<(String, usize)> = Vec::with_capacity(grouped.len());
9575                let mut flat: Vec<serde_json::Value> = Vec::new();
9576                for (key, children) in grouped.drain() {
9577                    order.push((key, children.len()));
9578                    flat.extend(children);
9579                }
9580
9581                embed_relationships(&mut *conn, ctx, &rel.target_type, requested, &mut flat)
9582                    .await?;
9583
9584                let mut rest = flat.into_iter();
9585                for (key, count) in order {
9586                    grouped.insert(key, rest.by_ref().take(count).collect());
9587                }
9588            }
9589
9590            for row in rows.iter_mut() {
9591                postrust_core::embed::attach_to_parent(row, &rel.name, &plan, &grouped);
9592            }
9593        }
9594
9595        Ok(())
9596    })
9597}
9598
9599/// Build a `where` document that addresses exactly one row by primary key.
9600///
9601/// Used by the by-PK mutations, which take the key columns as arguments instead
9602/// of a free-form `where`.
9603fn pk_where_from_args(
9604    ctx: &ResolverContext<'_>,
9605    schema_name: &str,
9606    table_name: &str,
9607    pk_columns: &[(String, String)],
9608    names: &crate::names::NameOverrides,
9609) -> Result<serde_json::Value, async_graphql::Error> {
9610    if pk_columns.is_empty() {
9611        return Err(async_graphql::Error::new(format!(
9612            "\"{}\" has no primary key, so it cannot be mutated by key",
9613            table_name
9614        )));
9615    }
9616
9617    // `update_x_by_pk` takes its key as one `pk_columns` object; the others
9618    // take the key columns as arguments of their own. Both spellings are what
9619    // a client was generated against, so both are read here.
9620    let from_object = ctx
9621        .args
9622        .try_get("pk_columns")
9623        .ok()
9624        .map(|v| accessor_to_json(&v));
9625
9626    let mut conditions = serde_json::Map::new();
9627    for (col_name, _) in pk_columns {
9628        // Under the name the key column is exposed as, on both sides: the
9629        // argument the client wrote, and the predicate handed on to the same
9630        // builder every other `where` goes through.
9631        let field = names
9632            .column(schema_name, table_name, col_name)
9633            .unwrap_or(col_name);
9634        let value = match &from_object {
9635            Some(serde_json::Value::Object(map)) => map.get(field).cloned().ok_or_else(|| {
9636                async_graphql::Error::new(format!(
9637                    "pk_columns is missing the key column \"{}\"",
9638                    field
9639                ))
9640            })?,
9641            _ => {
9642                let arg = ctx.args.try_get(field).map_err(|_| {
9643                    async_graphql::Error::new(format!(
9644                        "missing required primary key argument \"{}\"",
9645                        field
9646                    ))
9647                })?;
9648                accessor_to_json(&arg)
9649            }
9650        };
9651        conditions.insert(field.to_string(), serde_json::json!({ "_eq": value }));
9652    }
9653
9654    Ok(serde_json::Value::Object(conditions))
9655}
9656
9657/// GraphQL scalar name to use for a primary key argument of the given
9658/// PostgreSQL type.
9659///
9660/// Falls back to `String` for anything that does not map to a plain scalar --
9661/// a composite or array key cannot be expressed as a single named argument, and
9662/// the value is cast to the column's type in SQL anyway.
9663fn pk_argument_type(pg_type: &str) -> String {
9664    let rendered = crate::types::pg_type_to_graphql(pg_type).to_string();
9665    if rendered.starts_with('[') {
9666        "String".to_string()
9667    } else {
9668        rendered
9669    }
9670}
9671
9672/// The public GraphQL type of a primary-key argument.
9673///
9674/// SQL execution still carries the PostgreSQL type beside the key and uses it
9675/// for casts and binding. This is only the schema-facing type.
9676fn exposed_pk_argument_type(
9677    names: &crate::names::NameOverrides,
9678    schema: &str,
9679    table: &str,
9680    column: &str,
9681    pg_type: &str,
9682) -> String {
9683    names
9684        .column_type(schema, table, column)
9685        .map(|given| given.to_string())
9686        .unwrap_or_else(|| pk_argument_type(pg_type))
9687}
9688
9689/// Convert a GraphQL type string to a TypeRef.
9690fn graphql_type_ref(type_str: &str) -> TypeRef {
9691    // Parse type string like "[Users!]!" or "String" or "Int!"
9692    let is_list = type_str.starts_with('[');
9693    let is_nn = type_str.ends_with('!');
9694
9695    // Strip outer modifiers: first the trailing !, then the brackets
9696    let inner = if is_list {
9697        let stripped = type_str
9698            .trim_end_matches('!') // Remove outer !
9699            .trim_start_matches('[') // Remove [
9700            .trim_end_matches(']'); // Remove ]
9701        stripped
9702    } else {
9703        type_str.trim_end_matches('!')
9704    };
9705
9706    let inner_nn = inner.ends_with('!');
9707    let base_type = inner.trim_end_matches('!');
9708
9709    if is_list {
9710        if is_nn {
9711            if inner_nn {
9712                TypeRef::named_nn_list_nn(base_type)
9713            } else {
9714                TypeRef::named_list_nn(base_type)
9715            }
9716        } else if inner_nn {
9717            TypeRef::named_nn_list(base_type)
9718        } else {
9719            TypeRef::named_list(base_type)
9720        }
9721    } else if is_nn {
9722        TypeRef::named_nn(base_type)
9723    } else {
9724        TypeRef::named(base_type)
9725    }
9726}
9727
9728/// The key serde_json wraps a number in when it is keeping its digits.
9729///
9730/// This crate reads `serde_json` with `arbitrary_precision`, so a PostgreSQL
9731/// `numeric` survives a round trip through JSON with every digit it had. The
9732/// cost is on the way in: a fractional number in a *variable* is deserialized
9733/// into async-graphql's own value type as a one-key object holding the text,
9734/// because the generic path cannot know it is looking at a number. Integers
9735/// are unaffected, which is why this went unnoticed until a shape arrived by
9736/// variable and every coordinate in it read as zero.
9737const NUMBER_AS_TEXT: &str = "$serde_json::private::Number";
9738
9739/// The number an arbitrary-precision wrapper is holding, if that is what this
9740/// object is.
9741fn number_kept_as_text(
9742    map: &serde_json::Map<String, serde_json::Value>,
9743) -> Option<serde_json::Value> {
9744    if map.len() != 1 {
9745        return None;
9746    }
9747    let text = map.get(NUMBER_AS_TEXT)?.as_str()?;
9748    serde_json::from_str::<serde_json::Value>(text)
9749        .ok()
9750        .filter(|value| value.is_number())
9751}
9752
9753/// The same, applied to a whole document that came in as one.
9754fn plain_numbers(value: serde_json::Value) -> serde_json::Value {
9755    match value {
9756        serde_json::Value::Object(map) => match number_kept_as_text(&map) {
9757            Some(number) => number,
9758            None => serde_json::Value::Object(
9759                map.into_iter()
9760                    .map(|(k, v)| (k, plain_numbers(v)))
9761                    .collect(),
9762            ),
9763        },
9764        serde_json::Value::Array(items) => {
9765            serde_json::Value::Array(items.into_iter().map(plain_numbers).collect())
9766        }
9767        other => other,
9768    }
9769}
9770
9771/// Convert ValueAccessor to JSON.
9772fn accessor_to_json(accessor: &ValueAccessor<'_>) -> serde_json::Value {
9773    // Use the deserialize method if available, or convert manually
9774    if accessor.is_null() {
9775        serde_json::Value::Null
9776    } else if let Ok(b) = accessor.boolean() {
9777        serde_json::Value::Bool(b)
9778    } else if let Ok(i) = accessor.i64() {
9779        serde_json::Value::Number(i.into())
9780    } else if let Ok(f) = accessor.f64() {
9781        serde_json::Number::from_f64(f)
9782            .map(serde_json::Value::Number)
9783            .unwrap_or(serde_json::Value::Null)
9784    } else if let Ok(s) = accessor.string() {
9785        serde_json::Value::String(s.to_string())
9786    } else if let Ok(name) = accessor.enum_name() {
9787        // An enum value is not a string to `string()`, and falling through to
9788        // null made every `order_by: {name: asc}` read as an empty direction.
9789        serde_json::Value::String(name.to_string())
9790    } else if let Ok(list) = accessor.list() {
9791        serde_json::Value::Array(list.iter().map(|v| accessor_to_json(&v)).collect())
9792    } else if let Ok(obj) = accessor.object() {
9793        let map: serde_json::Map<String, serde_json::Value> = obj
9794            .iter()
9795            .map(|(k, v)| (k.to_string(), accessor_to_json(&v)))
9796            .collect();
9797        match number_kept_as_text(&map) {
9798            Some(number) => number,
9799            None => serde_json::Value::Object(map),
9800        }
9801    } else {
9802        serde_json::Value::Null
9803    }
9804}
9805
9806/// Convert async-graphql Value to JSON.
9807fn value_to_json(value: &Value) -> serde_json::Value {
9808    match value {
9809        Value::Null => serde_json::Value::Null,
9810        Value::Boolean(b) => serde_json::Value::Bool(*b),
9811        Value::Number(n) => {
9812            if let Some(i) = n.as_i64() {
9813                serde_json::Value::Number(i.into())
9814            } else if let Some(f) = n.as_f64() {
9815                serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
9816            } else {
9817                serde_json::Value::Null
9818            }
9819        }
9820        Value::String(s) => serde_json::Value::String(s.clone()),
9821        Value::List(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
9822        Value::Object(obj) => {
9823            let map: serde_json::Map<String, serde_json::Value> = obj
9824                .iter()
9825                .map(|(k, v)| (k.to_string(), value_to_json(v)))
9826                .collect();
9827            serde_json::Value::Object(map)
9828        }
9829        Value::Binary(b) => serde_json::Value::String(base64::Engine::encode(
9830            &base64::engine::general_purpose::STANDARD,
9831            b,
9832        )),
9833        Value::Enum(e) => serde_json::Value::String(e.to_string()),
9834    }
9835}
9836
9837/// Convert JSON to async-graphql Value.
9838fn json_to_value(json: serde_json::Value) -> Value {
9839    match json {
9840        serde_json::Value::Null => Value::Null,
9841        serde_json::Value::Bool(b) => Value::Boolean(b),
9842        serde_json::Value::Number(n) => {
9843            if let Some(i) = n.as_i64() {
9844                Value::Number(i.into())
9845            } else if let Some(f) = n.as_f64() {
9846                Value::Number(async_graphql::Number::from_f64(f).unwrap())
9847            } else {
9848                Value::Null
9849            }
9850        }
9851        serde_json::Value::String(s) => Value::String(s),
9852        serde_json::Value::Array(arr) => Value::List(arr.into_iter().map(json_to_value).collect()),
9853        serde_json::Value::Object(obj) => {
9854            let map: indexmap::IndexMap<async_graphql::Name, Value> = obj
9855                .into_iter()
9856                .map(|(k, v)| (async_graphql::Name::new(k), json_to_value(v)))
9857                .collect();
9858            Value::Object(map)
9859        }
9860    }
9861}
9862
9863/// Register filter input types.
9864///
9865/// These are currently unreachable: the `filter` and `where` arguments are
9866/// declared as the `JSON` scalar, so no field references these input objects
9867/// and async-graphql prunes them from the published schema (introspecting
9868/// `IntFilterInput` returns null). They are kept as the shape to move to if
9869/// filters become typed per column; until then the operators a filter actually
9870/// supports are the ones `build_where_clause` implements, and it rejects
9871/// anything else rather than ignoring it.
9872#[cfg(test)]
9873mod tests {
9874    use super::*;
9875    use indexmap::IndexMap;
9876    use postrust_core::schema_cache::{Cardinality, Column, Relationship, Table};
9877    use postrust_core::QualifiedIdentifier;
9878    use std::collections::{HashMap, HashSet};
9879
9880    fn entity_key_column(pg_type: &str, explicit_id: bool) -> EntityKeyColumn {
9881        EntityKeyColumn {
9882            field_name: "id".to_string(),
9883            column_name: "id".to_string(),
9884            pg_type: pg_type.to_string(),
9885            explicit_id,
9886        }
9887    }
9888
9889    #[test]
9890    fn federation_integer_keys_reject_strings() {
9891        let column = entity_key_column("int4", false);
9892
9893        assert_eq!(
9894            normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap(),
9895            serde_json::json!(2)
9896        );
9897        let error = normalize_entity_key_value("users", &column, &serde_json::json!("2"))
9898            .expect_err("a GraphQL Int cannot be a string");
9899        assert_eq!(
9900            error.message,
9901            "entity representation for \"users\" has invalid key field \"id\": expected a value compatible with PostgreSQL type \"int4\", found a string"
9902        );
9903    }
9904
9905    #[test]
9906    fn federation_bigint_keys_accept_exact_decimal_strings() {
9907        let column = entity_key_column("int8", false);
9908
9909        assert_eq!(
9910            normalize_entity_key_value("users", &column, &serde_json::json!(7)).unwrap(),
9911            serde_json::json!(7)
9912        );
9913        assert_eq!(
9914            normalize_entity_key_value("users", &column, &serde_json::json!("9007199254740993"))
9915                .unwrap(),
9916            serde_json::json!(9_007_199_254_740_993_i64)
9917        );
9918        assert!(normalize_entity_key_value(
9919            "users",
9920            &column,
9921            &serde_json::json!("9223372036854775808")
9922        )
9923        .is_err());
9924        assert!(normalize_entity_key_value("users", &column, &serde_json::json!("7.5")).is_err());
9925    }
9926
9927    #[test]
9928    fn federation_numeric_keys_accept_exact_decimal_strings() {
9929        let column = entity_key_column("numeric", false);
9930
9931        assert_eq!(
9932            normalize_entity_key_value("users", &column, &serde_json::json!(7.5)).unwrap(),
9933            serde_json::json!("7.5")
9934        );
9935        assert_eq!(
9936            normalize_entity_key_value(
9937                "users",
9938                &column,
9939                &serde_json::json!("9007199254740993.123456789")
9940            )
9941            .unwrap(),
9942            serde_json::json!("9007199254740993.123456789")
9943        );
9944        assert!(
9945            normalize_entity_key_value("users", &column, &serde_json::json!("not-a-number"))
9946                .is_err()
9947        );
9948    }
9949
9950    #[test]
9951    fn federation_integer_backed_ids_have_one_normal_form() {
9952        let column = entity_key_column("int4", true);
9953
9954        let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
9955        let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
9956        assert_eq!(number, serde_json::json!(2));
9957        assert_eq!(string, number);
9958        assert!(normalize_entity_key_value("users", &column, &serde_json::json!("two")).is_err());
9959    }
9960
9961    #[test]
9962    fn federation_text_backed_ids_normalize_integer_input_to_text() {
9963        let column = entity_key_column("text", true);
9964
9965        let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
9966        let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
9967        assert_eq!(number, serde_json::json!("2"));
9968        assert_eq!(string, number);
9969    }
9970
9971    #[test]
9972    fn federation_uuid_backed_ids_are_validated_and_canonicalized() {
9973        let column = entity_key_column("uuid", true);
9974
9975        assert_eq!(
9976            normalize_entity_key_value(
9977                "users",
9978                &column,
9979                &serde_json::json!("550E8400-E29B-41D4-A716-446655440000")
9980            )
9981            .unwrap(),
9982            serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
9983        );
9984        assert!(
9985            normalize_entity_key_value("users", &column, &serde_json::json!("not-a-uuid")).is_err()
9986        );
9987    }
9988
9989    fn create_test_table(name: &str) -> Table {
9990        let mut columns = IndexMap::new();
9991        columns.insert(
9992            "id".into(),
9993            Column {
9994                name: "id".into(),
9995                description: None,
9996                nullable: false,
9997                data_type: "integer".into(),
9998                nominal_type: "int4".into(),
9999                max_len: None,
10000                default: Some("nextval('id_seq')".into()),
10001                enum_values: vec![],
10002                is_pk: true,
10003                position: 1,
10004                always_generated: false,
10005                domain_type: None,
10006            },
10007        );
10008        columns.insert(
10009            "name".into(),
10010            Column {
10011                name: "name".into(),
10012                description: None,
10013                nullable: false,
10014                data_type: "text".into(),
10015                nominal_type: "text".into(),
10016                max_len: None,
10017                default: None,
10018                enum_values: vec![],
10019                is_pk: false,
10020                position: 2,
10021                always_generated: false,
10022                domain_type: None,
10023            },
10024        );
10025
10026        Table {
10027            schema: "public".into(),
10028            name: name.into(),
10029            description: None,
10030            is_view: false,
10031            insertable: true,
10032            updatable: true,
10033            deletable: true,
10034            pk_cols: vec!["id".into()],
10035            unique_constraints: Vec::new(),
10036            columns,
10037            computed_columns: Default::default(),
10038            is_partitioned: false,
10039        }
10040    }
10041
10042    fn create_test_schema_cache() -> SchemaCache {
10043        let mut tables = HashMap::new();
10044        let users = create_test_table("users");
10045        tables.insert(users.qualified_identifier(), users);
10046
10047        SchemaCache {
10048            tables,
10049            relationships: HashMap::new(),
10050            routines: HashMap::new(),
10051            timezones: HashSet::new(),
10052            media_handlers: HashMap::new(),
10053            pg_version: 150000,
10054            representations: Default::default(),
10055        }
10056    }
10057
10058    fn add_test_table(cache: &mut SchemaCache, name: &str) {
10059        let table = create_test_table(name);
10060        cache.tables.insert(table.qualified_identifier(), table);
10061    }
10062
10063    fn add_test_posts_relationship(cache: &mut SchemaCache) {
10064        let users = QualifiedIdentifier::new("public", "users");
10065        cache.relationships.insert(
10066            (users.clone(), "public".into()),
10067            vec![Relationship::ForeignKey {
10068                table: users,
10069                foreign_table: QualifiedIdentifier::new("public", "posts"),
10070                is_self: false,
10071                cardinality: Cardinality::O2M {
10072                    constraint: "posts_user_id_fkey".into(),
10073                    columns: vec![("id".into(), "user_id".into())],
10074                },
10075                table_is_view: false,
10076                foreign_table_is_view: false,
10077                constraint_name: "posts_user_id_fkey".into(),
10078            }],
10079        );
10080    }
10081
10082    // ============================================================================
10083    // Type Reference Tests
10084    // ============================================================================
10085
10086    #[test]
10087    fn test_graphql_type_ref_simple() {
10088        let _type_ref = graphql_type_ref("String");
10089        // TypeRef doesn't implement PartialEq, so we just test it doesn't panic
10090    }
10091
10092    #[test]
10093    fn test_graphql_type_ref_non_null() {
10094        let _type_ref = graphql_type_ref("String!");
10095    }
10096
10097    #[test]
10098    fn test_graphql_type_ref_list() {
10099        let _type_ref = graphql_type_ref("[String]");
10100    }
10101
10102    #[test]
10103    fn test_graphql_type_ref_list_non_null() {
10104        let _type_ref = graphql_type_ref("[String!]!");
10105    }
10106
10107    // ============================================================================
10108    // Value Conversion Tests
10109    // ============================================================================
10110
10111    #[test]
10112    fn test_value_to_json_null() {
10113        let value = Value::Null;
10114        let json = value_to_json(&value);
10115        assert_eq!(json, serde_json::Value::Null);
10116    }
10117
10118    #[test]
10119    fn test_value_to_json_boolean() {
10120        let value = Value::Boolean(true);
10121        let json = value_to_json(&value);
10122        assert_eq!(json, serde_json::Value::Bool(true));
10123    }
10124
10125    #[test]
10126    fn test_value_to_json_number() {
10127        let value = Value::Number(42.into());
10128        let json = value_to_json(&value);
10129        assert_eq!(json, serde_json::json!(42));
10130    }
10131
10132    #[test]
10133    fn test_value_to_json_string() {
10134        let value = Value::String("hello".to_string());
10135        let json = value_to_json(&value);
10136        assert_eq!(json, serde_json::Value::String("hello".to_string()));
10137    }
10138
10139    #[test]
10140    fn test_value_to_json_list() {
10141        let value = Value::List(vec![Value::Number(1.into()), Value::Number(2.into())]);
10142        let json = value_to_json(&value);
10143        assert_eq!(json, serde_json::json!([1, 2]));
10144    }
10145
10146    #[test]
10147    fn test_json_to_value_null() {
10148        let json = serde_json::Value::Null;
10149        let value = json_to_value(json);
10150        assert!(matches!(value, Value::Null));
10151    }
10152
10153    #[test]
10154    fn test_json_to_value_boolean() {
10155        let json = serde_json::Value::Bool(false);
10156        let value = json_to_value(json);
10157        assert!(matches!(value, Value::Boolean(false)));
10158    }
10159
10160    #[test]
10161    fn test_json_to_value_number() {
10162        let json = serde_json::json!(123);
10163        let value = json_to_value(json);
10164        assert!(matches!(value, Value::Number(_)));
10165    }
10166
10167    #[test]
10168    fn test_json_to_value_string() {
10169        let json = serde_json::Value::String("test".to_string());
10170        let value = json_to_value(json);
10171        assert!(matches!(value, Value::String(_)));
10172    }
10173
10174    #[test]
10175    fn test_json_to_value_array() {
10176        let json = serde_json::json!([1, 2, 3]);
10177        let value = json_to_value(json);
10178        assert!(matches!(value, Value::List(_)));
10179    }
10180
10181    #[test]
10182    fn test_json_to_value_object() {
10183        let json = serde_json::json!({"key": "value"});
10184        let value = json_to_value(json);
10185        assert!(matches!(value, Value::Object(_)));
10186    }
10187
10188    // ============================================================================
10189    // Schema Building Tests
10190    // ============================================================================
10191
10192    #[test]
10193    fn test_build_dynamic_schema() {
10194        let cache = create_test_schema_cache();
10195        let config = SchemaConfig::default();
10196        let generated = build_schema(&cache, &config);
10197
10198        let result = build_dynamic_schema(
10199            &generated,
10200            &cache,
10201            None,
10202            None,
10203            Arc::new(Default::default()),
10204            std::time::Duration::from_secs(30),
10205            None,
10206        );
10207        if let Err(ref e) = result {
10208            eprintln!("Schema build error: {:?}", e);
10209        }
10210        assert!(result.is_ok(), "Schema build failed: {:?}", result.err());
10211    }
10212
10213    #[test]
10214    fn a_column_type_override_reaches_fields_and_key_arguments() {
10215        let cache = create_test_schema_cache();
10216        let names = crate::names::NameOverrides::parse(
10217            r#"{"tables": {"public.users": {"column_types": {"id": "ID"}}}}"#,
10218        )
10219        .unwrap();
10220        let config = SchemaConfig {
10221            names: names.clone(),
10222            ..SchemaConfig::default()
10223        };
10224        let generated = build_schema(&cache, &config);
10225        let schema = build_dynamic_schema(
10226            &generated,
10227            &cache,
10228            None,
10229            None,
10230            Arc::new(names),
10231            std::time::Duration::from_secs(30),
10232            None,
10233        )
10234        .expect("schema with an ID override should build");
10235        let sdl = schema.sdl();
10236
10237        let input_body = |name: &str| {
10238            sdl.split_once(&format!("input {name} {{"))
10239                .and_then(|(_, rest)| rest.split_once('}'))
10240                .map(|(body, _)| body.to_string())
10241                .unwrap_or_else(|| panic!("missing input {name}:\n{sdl}"))
10242        };
10243
10244        assert!(sdl.contains("id: ID!"), "object and input fields:\n{sdl}");
10245        assert!(
10246            sdl.contains("users_by_pk(id: ID!): users"),
10247            "query key argument:\n{sdl}"
10248        );
10249        assert!(
10250            sdl.contains("delete_users_by_pk(id: ID!): users"),
10251            "delete key argument:\n{sdl}"
10252        );
10253        assert!(
10254            input_body("users_pk_columns_input").contains("id: ID!"),
10255            "update key input:\n{sdl}"
10256        );
10257        assert!(
10258            input_body("users_bool_exp").contains("id: ID_comparison_exp"),
10259            "comparison input:\n{sdl}"
10260        );
10261        assert!(
10262            input_body("users_insert_input").contains("id: ID"),
10263            "write input:\n{sdl}"
10264        );
10265    }
10266
10267    #[test]
10268    fn a_type_prefix_names_the_complete_dynamic_schema() {
10269        let cache = create_test_schema_cache();
10270        let config = SchemaConfig {
10271            type_prefix: Some("test".into()),
10272            ..SchemaConfig::default()
10273        };
10274        let generated = build_schema(&cache, &config);
10275        let schema = build_dynamic_schema(
10276            &generated,
10277            &cache,
10278            None,
10279            None,
10280            Arc::new(Default::default()),
10281            std::time::Duration::from_secs(30),
10282            None,
10283        )
10284        .expect("a prefixed schema should build");
10285        let sdl = schema.sdl();
10286
10287        assert!(sdl.contains("type test_users "), "object type:\n{sdl}");
10288        assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10289        assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10290        assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10291        assert!(
10292            sdl.contains("test_users_aggregate"),
10293            "aggregate type:\n{sdl}"
10294        );
10295        assert!(sdl.contains("test_insert_users("), "mutation root:\n{sdl}");
10296    }
10297
10298    #[test]
10299    fn federation_is_absent_by_default() {
10300        let cache = create_test_schema_cache();
10301        let generated = build_schema(&cache, &SchemaConfig::default());
10302        let schema = build_dynamic_schema(
10303            &generated,
10304            &cache,
10305            None,
10306            None,
10307            Arc::new(Default::default()),
10308            std::time::Duration::from_secs(30),
10309            None,
10310        )
10311        .expect("a non-federated schema should build");
10312        let sdl = schema.sdl();
10313
10314        assert!(!sdl.contains("_Service"), "service type:\n{sdl}");
10315        assert!(!sdl.contains("_service"), "service field:\n{sdl}");
10316        assert!(!sdl.contains("@key"), "federation key:\n{sdl}");
10317    }
10318
10319    #[test]
10320    fn federation_entity_selections_respect_fragment_type_conditions() {
10321        let document = async_graphql::parser::parse_query(
10322            r#"
10323            query {
10324              _entities(representations: []) {
10325                __typename
10326                ... on authors { id items { title } }
10327                ...ShopFields
10328                ... { common }
10329                ...EntityName
10330              }
10331            }
10332            fragment ShopFields on shops { id items { sku } }
10333            fragment EntityName on _Entity { __typename }
10334            "#,
10335        )
10336        .expect("query parses");
10337        let (_, operation) = document.operations.iter().next().expect("one operation");
10338        let entity = operation
10339            .node
10340            .selection_set
10341            .node
10342            .items
10343            .iter()
10344            .find_map(|selection| match &selection.node {
10345                Selection::Field(field) if field.node.name.node == "_entities" => Some(&field.node),
10346                _ => None,
10347            })
10348            .expect("_entities field");
10349
10350        assert_eq!(
10351            entity_field_applicability(&entity.selection_set.node, &document.fragments, "authors"),
10352            vec![true, true, true, false, false, true, true]
10353        );
10354        assert_eq!(
10355            entity_field_applicability(&entity.selection_set.node, &document.fragments, "shops"),
10356            vec![true, false, false, true, true, true, true]
10357        );
10358    }
10359
10360    #[tokio::test]
10361    async fn federation_entity_lists_allow_null_members() {
10362        let entity =
10363            Object::new("widgets").field(Field::new("id", TypeRef::named_nn(TypeRef::INT), |_| {
10364                FieldFuture::new(async { Ok(Some(Value::from(2))) })
10365            }));
10366        let union = Union::new("_Entity").possible_type("widgets");
10367        let query = Object::new("Query").field(Field::new(
10368            "_entities",
10369            TypeRef::named_list_nn("_Entity"),
10370            |_| {
10371                FieldFuture::new(async {
10372                    Ok(Some(FieldValue::list([
10373                        FieldValue::NULL,
10374                        FieldValue::NULL.with_type("widgets"),
10375                    ])))
10376                })
10377            },
10378        ));
10379        let schema = Schema::build("Query", None, None)
10380            .extension(NullableFederationEntity)
10381            .register(entity)
10382            .register(union)
10383            .register(query)
10384            .finish()
10385            .expect("schema builds");
10386
10387        let response = schema
10388            .execute("{ _entities { ... on widgets { id } } }")
10389            .await;
10390
10391        assert!(response.errors.is_empty(), "{:?}", response.errors);
10392        assert_eq!(
10393            response.data,
10394            async_graphql::value!({ "_entities": [null, { "id": 2 }] })
10395        );
10396    }
10397
10398    #[test]
10399    fn federation_metadata_is_byte_for_byte_inert_when_disabled() {
10400        let cache = create_test_schema_cache();
10401        let sdl_for = |config: SchemaConfig| {
10402            let generated = build_schema(&cache, &config);
10403            build_dynamic_schema(
10404                &generated,
10405                &cache,
10406                None,
10407                None,
10408                Arc::new(config.names.clone()),
10409                std::time::Duration::from_secs(30),
10410                None,
10411            )
10412            .expect("schema should build")
10413            .sdl()
10414        };
10415
10416        let baseline = sdl_for(SchemaConfig {
10417            type_prefix: Some("test".into()),
10418            ..SchemaConfig::default()
10419        });
10420        let with_disabled_federation_metadata = sdl_for(SchemaConfig {
10421            enable_federation: false,
10422            type_prefix: Some("test".into()),
10423            names: crate::names::NameOverrides::parse(
10424                r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10425            )
10426            .expect("metadata"),
10427            ..SchemaConfig::default()
10428        });
10429
10430        assert_eq!(with_disabled_federation_metadata, baseline);
10431        assert!(
10432            with_disabled_federation_metadata.contains("type test_users "),
10433            "prefixed object type remains when federation is disabled:\n{with_disabled_federation_metadata}"
10434        );
10435        assert!(
10436            !with_disabled_federation_metadata.contains("_Service"),
10437            "service type:\n{with_disabled_federation_metadata}"
10438        );
10439        assert!(
10440            !with_disabled_federation_metadata.contains("@key"),
10441            "federation key:\n{with_disabled_federation_metadata}"
10442        );
10443    }
10444
10445    #[test]
10446    fn shared_entities_make_relationship_fields_shareable() {
10447        let federation_sdl = |shared: bool| {
10448            let mut cache = create_test_schema_cache();
10449            add_test_table(&mut cache, "posts");
10450            add_test_posts_relationship(&mut cache);
10451            let names = match shared {
10452                true => crate::names::NameOverrides::parse(
10453                    r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10454                )
10455                .expect("metadata"),
10456                false => crate::names::NameOverrides::default(),
10457            };
10458            let config = SchemaConfig {
10459                enable_federation: true,
10460                type_prefix: Some("test".into()),
10461                names: names.clone(),
10462                ..SchemaConfig::default()
10463            };
10464            let generated = build_schema(&cache, &config);
10465            build_dynamic_schema(
10466                &generated,
10467                &cache,
10468                None,
10469                None,
10470                Arc::new(names),
10471                std::time::Duration::from_secs(30),
10472                None,
10473            )
10474            .expect("federated schema should build")
10475            .sdl_with_options(async_graphql::SDLExportOptions::new().federation())
10476        };
10477
10478        let shared = federation_sdl(true);
10479        assert!(
10480            shared.contains("): [test_posts!]! @shareable"),
10481            "shared relationship field:\n{shared}"
10482        );
10483        assert!(
10484            shared.contains("): test_posts_aggregate! @shareable"),
10485            "shared relationship aggregate field:\n{shared}"
10486        );
10487
10488        let not_shared = federation_sdl(false);
10489        assert!(
10490            not_shared.contains("): [test_posts!]!"),
10491            "non-shared relationship field:\n{not_shared}"
10492        );
10493        assert!(
10494            !not_shared.contains("): [test_posts!]! @shareable"),
10495            "non-shared relationship field must not be shareable:\n{not_shared}"
10496        );
10497        assert!(
10498            not_shared.contains("): test_posts_aggregate!"),
10499            "non-shared relationship aggregate field:\n{not_shared}"
10500        );
10501        assert!(
10502            !not_shared.contains("): test_posts_aggregate! @shareable"),
10503            "non-shared relationship aggregate must not be shareable:\n{not_shared}"
10504        );
10505    }
10506
10507    #[tokio::test]
10508    async fn federation_exposes_service_sdl_and_entity_keys() {
10509        let mut cache = create_test_schema_cache();
10510        add_test_table(&mut cache, "posts");
10511        let config = SchemaConfig {
10512            enable_federation: true,
10513            type_prefix: Some("test".into()),
10514            names: crate::names::NameOverrides::parse(
10515                r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10516            )
10517            .expect("metadata"),
10518            ..SchemaConfig::default()
10519        };
10520        let generated = build_schema(&cache, &config);
10521        let schema = build_dynamic_schema(
10522            &generated,
10523            &cache,
10524            None,
10525            None,
10526            Arc::new(Default::default()),
10527            std::time::Duration::from_secs(30),
10528            None,
10529        )
10530        .expect("a federated schema should build");
10531        let sdl = schema.sdl();
10532        let federation_sdl =
10533            schema.sdl_with_options(async_graphql::SDLExportOptions::new().federation());
10534
10535        assert!(sdl.contains("_Service"), "service type:\n{sdl}");
10536        assert!(sdl.contains("_service"), "service field:\n{sdl}");
10537        assert!(
10538            federation_sdl.contains("type users @key(fields: \"id\")"),
10539            "users key:\n{federation_sdl}"
10540        );
10541        assert!(
10542            federation_sdl.contains("@shareable"),
10543            "shared field directive:\n{federation_sdl}"
10544        );
10545        assert!(sdl.contains("type users "), "shared object type:\n{sdl}");
10546        assert!(
10547            !sdl.contains("type test_users "),
10548            "prefixed shared object type:\n{sdl}"
10549        );
10550        assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10551        assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10552        assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10553        assert!(
10554            !sdl.contains("input users_bool_exp "),
10555            "unprefixed derived input:\n{sdl}"
10556        );
10557        assert!(
10558            sdl.contains("test_users_aggregate"),
10559            "aggregate type:\n{sdl}"
10560        );
10561        assert!(
10562            !sdl.contains("type users_aggregate "),
10563            "unprefixed aggregate type:\n{sdl}"
10564        );
10565        assert!(
10566            sdl.contains("test_users_mutation_response"),
10567            "mutation response type:\n{sdl}"
10568        );
10569        assert!(
10570            !sdl.contains("type users_mutation_response "),
10571            "unprefixed mutation response type:\n{sdl}"
10572        );
10573        assert!(
10574            sdl.contains("type test_posts {"),
10575            "non-shared object type remains prefixed:\n{sdl}"
10576        );
10577        assert!(
10578            !sdl.contains("type posts {"),
10579            "non-shared object type must not become unprefixed:\n{sdl}"
10580        );
10581        assert!(sdl.contains("test_posts("), "prefixed posts root:\n{sdl}");
10582        assert!(
10583            sdl.contains("test_posts_bool_exp"),
10584            "prefixed posts bool_exp:\n{sdl}"
10585        );
10586
10587        let response = schema.execute("{ _service { sdl } }").await;
10588        assert!(
10589            response.errors.is_empty(),
10590            "service query errors: {:?}",
10591            response.errors
10592        );
10593        let service = response.data.into_json().expect("service response JSON");
10594        let service_sdl = service
10595            .get("_service")
10596            .and_then(|value| value.get("sdl"))
10597            .and_then(serde_json::Value::as_str)
10598            .expect("service SDL string");
10599        assert!(
10600            service_sdl.contains("type Query"),
10601            "federation root type:\n{service_sdl}"
10602        );
10603        assert!(
10604            service_sdl.contains("test_users("),
10605            "query fields are on the federation root:\n{service_sdl}"
10606        );
10607        assert!(
10608            service_sdl.contains("test_posts("),
10609            "non-shared query fields are on the federation root:\n{service_sdl}"
10610        );
10611        assert!(
10612            !service_sdl.contains("type query_root"),
10613            "Apollo composition treats query_root as an ordinary object without a schema definition:\n{service_sdl}"
10614        );
10615        assert!(
10616            service_sdl.contains("type users @key(fields: \"id\")"),
10617            "{service_sdl}"
10618        );
10619        assert!(
10620            service_sdl.contains("type test_posts @key(fields: \"id\")"),
10621            "{service_sdl}"
10622        );
10623    }
10624
10625    /// `_exists` becomes a subselect over the table it names, correlated with
10626    /// nothing -- which is the difference between it and a relationship.
10627    #[test]
10628    fn exists_is_a_subselect_over_another_table() {
10629        let cache = create_test_schema_cache();
10630        let names = crate::names::NameOverrides::default();
10631        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10632        let scope = WhereScope::table("public", "users", "users", &names)
10633            .with_resolution(&cache, &relationships);
10634
10635        let mut param_idx = 1usize;
10636        let mut values: Vec<serde_json::Value> = Vec::new();
10637        let mut aliases = 0usize;
10638        let sql = build_condition(
10639            &serde_json::json!({
10640                "_exists": {"_table": "users", "_where": {"name": {"_eq": "Ann"}}}
10641            }),
10642            &scope,
10643            &mut param_idx,
10644            &mut values,
10645            &mut aliases,
10646        )
10647        .expect("compiles")
10648        .expect("constrains something");
10649
10650        assert_eq!(
10651            sql,
10652            "EXISTS (SELECT 1 FROM \"public\".\"users\" AS \"pgrst_exists_1\" \
10653             WHERE \"pgrst_exists_1\".\"name\" = $1::text)"
10654        );
10655        assert_eq!(values, vec![serde_json::json!("Ann")]);
10656    }
10657
10658    /// A `_table` nobody has is refused rather than written into the query.
10659    #[test]
10660    fn exists_names_a_table_the_server_has() {
10661        let cache = create_test_schema_cache();
10662        let names = crate::names::NameOverrides::default();
10663        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10664        let scope = WhereScope::table("public", "users", "users", &names)
10665            .with_resolution(&cache, &relationships);
10666        let error = build_condition(
10667            &serde_json::json!({"_exists": {"_table": "nowhere", "_where": {}}}),
10668            &scope,
10669            &mut 1,
10670            &mut Vec::new(),
10671            &mut 0,
10672        )
10673        .expect_err("a table that is not there is an error");
10674        assert!(
10675            error.message.contains("public.nowhere"),
10676            "{}",
10677            error.message
10678        );
10679    }
10680
10681    /// A role granted "how many" and not "which" gets the count and no rows.
10682    ///
10683    /// A select permission naming no columns, with `allow_aggregations`, is
10684    /// Hasura's way of writing that. The table has an aggregate root and no
10685    /// row type, so `nodes` is not there to be a list of one -- and `count`
10686    /// takes no `columns`, because there are none to name.
10687    #[test]
10688    fn a_table_a_role_may_count_and_not_read_has_a_count_and_no_rows() {
10689        let cache = create_test_schema_cache();
10690        let names = crate::names::NameOverrides::parse(
10691            r#"{"tables": {"public.users": {"permissions":
10692                 {"counter": {"select": {"columns": [], "filter": {},
10693                                         "allow_aggregations": true}}}}}}"#,
10694        )
10695        .unwrap();
10696        let view = crate::role::cache_for_role(&cache, &names, "counter", false);
10697        assert!(!view.tables.is_empty(), "the table is there to be counted");
10698
10699        let config = SchemaConfig {
10700            names: names.clone(),
10701            role: Some("counter".to_string()),
10702            ..SchemaConfig::default()
10703        };
10704        let generated = build_schema(&view, &config);
10705        let schema = build_dynamic_schema(
10706            &generated,
10707            &view,
10708            None,
10709            None,
10710            Arc::new(names),
10711            std::time::Duration::from_secs(30),
10712            Some("counter"),
10713        )
10714        .expect("a role that may only count still has a buildable schema");
10715        let sdl = schema.sdl();
10716
10717        assert!(
10718            sdl.contains("users_aggregate"),
10719            "the count is there:\n{}",
10720            sdl
10721        );
10722        assert!(sdl.contains("count: Int!"), "and it is a count:\n{}", sdl);
10723        // No row type, so nothing that would answer with one.
10724        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10725        assert!(
10726            !sdl.contains("nodes"),
10727            "and no rows beside the numbers:\n{}",
10728            sdl
10729        );
10730        assert!(
10731            !sdl.contains("users_select_column"),
10732            "no column enum to order by or count over:\n{}",
10733            sdl
10734        );
10735        // The functions that read column data are absent with the columns.
10736        assert!(!sdl.contains("users_max_fields"), "no max:\n{}", sdl);
10737    }
10738
10739    /// A role granted `insert` and no `select` gets the write and no type.
10740    ///
10741    /// Hasura's own corpus does this eight times over. What has to hold is
10742    /// that the schema still builds: `insert_users` is there, `users` is not,
10743    /// and the response the insert answers with has no `returning` to name a
10744    /// type that does not exist.
10745    #[test]
10746    fn a_table_a_role_may_only_write_gets_the_write_and_no_type() {
10747        let cache = create_test_schema_cache();
10748        let names = crate::names::NameOverrides::parse(
10749            r#"{"tables": {"public.users": {"permissions":
10750                 {"writer": {"insert": {"columns": "*", "check": {}}}}}}}"#,
10751        )
10752        .unwrap();
10753        let view = crate::role::cache_for_role(&cache, &names, "writer", false);
10754        let config = SchemaConfig {
10755            names: names.clone(),
10756            role: Some("writer".to_string()),
10757            ..SchemaConfig::default()
10758        };
10759        let generated = build_schema(&view, &config);
10760
10761        let schema = build_dynamic_schema(
10762            &generated,
10763            &view,
10764            None,
10765            None,
10766            Arc::new(names),
10767            std::time::Duration::from_secs(30),
10768            Some("writer"),
10769        )
10770        .expect("a role that may only write still has a buildable schema");
10771        let sdl = schema.sdl();
10772
10773        assert!(
10774            sdl.contains("insert_users("),
10775            "the write is there:\n{}",
10776            sdl
10777        );
10778        assert!(
10779            sdl.contains("users_insert_input"),
10780            "and its input:\n{}",
10781            sdl
10782        );
10783        // No row type, and so none of the three fields that answer with one.
10784        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10785        assert!(!sdl.contains("insert_users_one"), "no insert_one:\n{}", sdl);
10786        assert!(
10787            !sdl.contains("returning"),
10788            "and nothing to return:\n{}",
10789            sdl
10790        );
10791        assert!(
10792            sdl.contains("users_mutation_response"),
10793            "the count is still answered:\n{}",
10794            sdl
10795        );
10796        assert!(
10797            !sdl.contains("_mutation_response_mutation_response"),
10798            "and no type is named twice over:\n{}",
10799            sdl
10800        );
10801    }
10802
10803    /// A cache with one VOLATILE function returning rows of `users`, which is
10804    /// the shape `add_to_score` has in Hasura's corpus.
10805    fn cache_with_a_mutation_function() -> SchemaCache {
10806        use postrust_core::schema_cache::{FuncVolatility, RetType, Routine};
10807
10808        let mut cache = create_test_schema_cache();
10809        let routine = Routine {
10810            schema: "public".into(),
10811            name: "add_to_score".into(),
10812            description: None,
10813            params: Vec::new(),
10814            return_type: RetType::SetOf("users".into()),
10815            returns_composite: true,
10816            volatility: FuncVolatility::Volatile,
10817            has_variadic: false,
10818            isolation_level: None,
10819            settings: Vec::new(),
10820            is_procedure: false,
10821            media_type: None,
10822            media_base_type: None,
10823            output_columns: Vec::new(),
10824        };
10825        cache
10826            .routines
10827            .insert(routine.qualified_identifier(), vec![routine]);
10828        cache
10829    }
10830
10831    /// A function with side effects is not granted by permission to read the
10832    /// table it returns. Hasura infers a query function's permission from
10833    /// that select and infers nothing for a mutation, so a role never named
10834    /// by a function permission has no mutation root at all.
10835    #[test]
10836    fn a_mutation_function_needs_the_role_to_be_named() {
10837        let cache = cache_with_a_mutation_function();
10838        let withheld = crate::names::NameOverrides::parse(
10839            r#"{"tables": {"public.users": {"permissions":
10840                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10841                "functions": {"public.add_to_score": {"exposed_as": "mutation"}}}"#,
10842        )
10843        .unwrap();
10844        let granted = crate::names::NameOverrides::parse(
10845            r#"{"tables": {"public.users": {"permissions":
10846                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10847                "functions": {"public.add_to_score":
10848                  {"exposed_as": "mutation", "roles": ["reader"]}}}"#,
10849        )
10850        .unwrap();
10851
10852        let sdl_for = |names: &crate::names::NameOverrides| {
10853            let view = crate::role::cache_for_role(&cache, names, "reader", false);
10854            let config = SchemaConfig {
10855                names: names.clone(),
10856                role: Some("reader".to_string()),
10857                ..SchemaConfig::default()
10858            };
10859            let generated = build_schema(&view, &config);
10860            build_dynamic_schema(
10861                &generated,
10862                &view,
10863                None,
10864                None,
10865                Arc::new(names.clone()),
10866                std::time::Duration::from_secs(30),
10867                Some("reader"),
10868            )
10869            .expect("schema builds")
10870            .sdl()
10871        };
10872
10873        let without = sdl_for(&withheld);
10874        assert!(
10875            !without.contains("add_to_score"),
10876            "not granted, so not there:\n{}",
10877            without
10878        );
10879        // And with no mutation of any kind left, there is no mutation root --
10880        // which is what `no mutations exist` is answered from.
10881        assert!(
10882            !without.contains("mutation: mutation_root"),
10883            "and no mutation root at all:\n{}",
10884            without
10885        );
10886
10887        let with = sdl_for(&granted);
10888        assert!(
10889            with.contains("add_to_score"),
10890            "named by a function permission, so it is:\n{}",
10891            with
10892        );
10893    }
10894
10895    /// An unrecognised role is a role that was granted nothing, not an error.
10896    #[test]
10897    fn a_role_the_document_never_names_is_granted_nothing() {
10898        let cache = Arc::new(create_test_schema_cache());
10899        let config = SchemaConfig {
10900            names: crate::names::NameOverrides::parse(
10901                r#"{"tables": {"public.users": {"permissions":
10902                     {"reader": {"select": {"columns": "*", "filter": {}}}}}}}"#,
10903            )
10904            .unwrap(),
10905            ..SchemaConfig::default()
10906        };
10907        let empty = build_ungranted_schema(&cache, &config).expect("it builds");
10908        let sdl = empty.sdl();
10909        assert!(
10910            sdl.contains("no_queries_available"),
10911            "nothing to read:\n{}",
10912            sdl
10913        );
10914        assert!(!sdl.contains("type users "), "and no table:\n{}", sdl);
10915        // A document with no permissions at all has no unrecognised roles.
10916        assert!(build_ungranted_schema(&cache, &SchemaConfig::default()).is_none());
10917    }
10918
10919    /// A column the role may write and not read is in the input and not in the
10920    /// type. The two column sets are the point: one schema cache, two answers.
10921    #[test]
10922    fn a_column_a_role_may_set_without_seeing_is_in_the_input_alone() {
10923        let cache = create_test_schema_cache();
10924        let names = crate::names::NameOverrides::parse(
10925            r#"{"tables": {"public.users": {"permissions":
10926                 {"user": {"select": {"columns": ["id"], "filter": {}},
10927                           "insert": {"columns": "*", "check": {}}}}}}}"#,
10928        )
10929        .unwrap();
10930        let view = crate::role::cache_for_role(&cache, &names, "user", false);
10931        let config = SchemaConfig {
10932            names: names.clone(),
10933            role: Some("user".to_string()),
10934            ..SchemaConfig::default()
10935        };
10936        let generated = build_schema(&view, &config);
10937        let object = &generated.object_types["users"];
10938        assert_eq!(
10939            object
10940                .fields
10941                .iter()
10942                .map(|f| f.name.as_str())
10943                .collect::<Vec<_>>(),
10944            vec!["id"],
10945            "the type shows what may be read"
10946        );
10947        assert!(
10948            object.writable_fields.iter().any(|f| f.name == "name"),
10949            "and the input keeps what may be written"
10950        );
10951
10952        let schema = build_dynamic_schema(
10953            &generated,
10954            &view,
10955            None,
10956            None,
10957            Arc::new(names),
10958            std::time::Duration::from_secs(30),
10959            Some("user"),
10960        )
10961        .expect("schema builds");
10962        let sdl = schema.sdl();
10963        let input = sdl
10964            .split("input users_insert_input")
10965            .nth(1)
10966            .and_then(|rest| rest.split('}').next())
10967            .expect("the insert input is registered");
10968        assert!(input.contains("name:"), "settable without being readable");
10969    }
10970
10971    /// A column PostgreSQL generates always is in the row type and in no
10972    /// write input. It is not a permission -- no role may name it -- so the
10973    /// refusal belongs to the schema rather than to the database.
10974    #[test]
10975    fn a_generated_column_is_readable_and_not_writable() {
10976        let mut cache = create_test_schema_cache();
10977        cache
10978            .tables
10979            .get_mut(&postrust_core::api_request::QualifiedIdentifier::new(
10980                "public", "users",
10981            ))
10982            .expect("the fixture has users")
10983            .columns
10984            .get_mut("id")
10985            .expect("with an id")
10986            .always_generated = true;
10987
10988        let names = crate::names::NameOverrides::default();
10989        let config = SchemaConfig {
10990            names: names.clone(),
10991            ..SchemaConfig::default()
10992        };
10993        let generated = build_schema(&cache, &config);
10994        let schema = build_dynamic_schema(
10995            &generated,
10996            &cache,
10997            None,
10998            None,
10999            Arc::new(names),
11000            std::time::Duration::from_secs(30),
11001            None,
11002        )
11003        .expect("schema builds");
11004        let sdl = schema.sdl();
11005
11006        let block = |header: &str| {
11007            sdl.split(header)
11008                .nth(1)
11009                .and_then(|rest| rest.split('}').next())
11010                .unwrap_or_else(|| panic!("{} is registered:\n{}", header, sdl))
11011                .to_string()
11012        };
11013        assert!(
11014            block("type users ").contains("id:"),
11015            "still readable:\n{}",
11016            sdl
11017        );
11018        for header in ["input users_insert_input", "input users_set_input"] {
11019            assert!(
11020                !block(header).contains("id:"),
11021                "{} may not name it:\n{}",
11022                header,
11023                block(header)
11024            );
11025        }
11026        // The table's only number was the generated column, so there is
11027        // nothing left to add to and no type for adding to it.
11028        assert!(
11029            !sdl.contains("users_inc_input"),
11030            "and nothing is left to increment:\n{}",
11031            sdl
11032        );
11033        assert!(
11034            block("input users_insert_input").contains("name:"),
11035            "and the rest of the table is untouched",
11036        );
11037    }
11038
11039    #[test]
11040    fn test_create_object_type() {
11041        let table = create_test_table("users");
11042        let obj = TableObjectType::from_table(&table);
11043        let _gql_obj = create_object_type(&obj, &[], None);
11044    }
11045
11046    #[test]
11047    fn test_create_query_type() {
11048        let cache = create_test_schema_cache();
11049        let config = SchemaConfig::default();
11050        let generated = build_schema(&cache, &config);
11051
11052        let _query = create_query_type(
11053            &generated,
11054            "query_root",
11055            None,
11056            Arc::new(HashMap::new()),
11057            Arc::new(Default::default()),
11058        );
11059    }
11060
11061    #[test]
11062    fn test_create_mutation_type() {
11063        let cache = create_test_schema_cache();
11064        let config = SchemaConfig::default();
11065        let generated = build_schema(&cache, &config);
11066
11067        let _mutation = create_mutation_type(
11068            &generated,
11069            "mutation_root",
11070            Arc::new(HashMap::new()),
11071            Arc::new(HashMap::new()),
11072            Arc::new(Default::default()),
11073            None,
11074            None,
11075        );
11076    }
11077
11078    // ============================================================================
11079    // Scalar Tests
11080    // ============================================================================
11081
11082    #[test]
11083    fn a_scalar_is_named_the_way_a_client_declares_it() {
11084        use crate::types::{pg_type_to_graphql, GraphQLType};
11085
11086        // These names appear in the client's own queries -- `query ($x:
11087        // jsonb!)` names a type that has to exist under exactly that
11088        // spelling -- so they are asserted rather than left to the Display
11089        // impl.
11090        assert_eq!(pg_type_to_graphql("jsonb").to_string(), "jsonb");
11091        assert_eq!(pg_type_to_graphql("json").to_string(), "json");
11092        assert_eq!(pg_type_to_graphql("int8").to_string(), "bigint");
11093        assert_eq!(pg_type_to_graphql("numeric").to_string(), "numeric");
11094        assert_eq!(pg_type_to_graphql("timestamptz").to_string(), "timestamptz");
11095        assert_eq!(pg_type_to_graphql("timestamp").to_string(), "timestamp");
11096        assert_eq!(pg_type_to_graphql("uuid").to_string(), "uuid");
11097
11098        // A type this server knows nothing about keeps its own name, which is
11099        // how a PostGIS column and a database enum both become usable.
11100        assert_eq!(pg_type_to_graphql("geometry").to_string(), "geometry");
11101        assert_eq!(
11102            pg_type_to_graphql("colors_enum"),
11103            GraphQLType::Custom("colors_enum".to_string())
11104        );
11105
11106        // And the leaf of an array is what gets registered, not the array.
11107        assert_eq!(
11108            leaf_scalar_name(&pg_type_to_graphql("_text")),
11109            "String".to_string()
11110        );
11111    }
11112
11113    // ============================================================================
11114    // Filter Input Type Tests
11115    // ============================================================================
11116
11117    #[test]
11118    fn every_table_gets_a_boolean_expression() {
11119        let cache = create_test_schema_cache();
11120        let config = SchemaConfig::default();
11121        let generated = build_schema(&cache, &config);
11122
11123        let (inputs, _) = crate::input::bool_exp::build_inputs(
11124            &generated.object_types,
11125            &generated.relationship_fields,
11126        );
11127        let names: HashSet<String> = inputs.iter().map(|i| i.type_name().to_string()).collect();
11128
11129        for table in generated.object_types.keys() {
11130            let expected = format!("{}_bool_exp", table);
11131            assert!(
11132                names.contains(&expected),
11133                "no {} among {:?}",
11134                expected,
11135                names
11136            );
11137        }
11138        // The scalars the fixture's columns use.
11139        assert!(names.contains("String_comparison_exp"));
11140        assert!(names.contains("Int_comparison_exp"));
11141    }
11142
11143    #[test]
11144    fn a_schema_carrying_the_boolean_expressions_still_builds() {
11145        let cache = create_test_schema_cache();
11146        let config = SchemaConfig::default();
11147        let generated = build_schema(&cache, &config);
11148
11149        let schema = build_dynamic_schema(
11150            &generated,
11151            &cache,
11152            None,
11153            None,
11154            Arc::new(Default::default()),
11155            std::time::Duration::from_secs(30),
11156            None,
11157        );
11158        assert!(schema.is_ok(), "{:?}", schema.err());
11159
11160        let sdl = schema.unwrap().sdl();
11161        assert!(
11162            sdl.contains("_bool_exp"),
11163            "no boolean expressions in:\n{}",
11164            sdl
11165        );
11166        assert!(sdl.contains("_and"), "no _and in the generated expressions");
11167    }
11168
11169    // ============================================================================
11170    // Subscription Tests
11171    // ============================================================================
11172
11173    #[test]
11174    fn test_build_schema_with_subscriptions() {
11175        let cache = create_test_schema_cache();
11176        let config = SchemaConfig {
11177            enable_subscriptions: true,
11178            ..SchemaConfig::default()
11179        };
11180        let generated = build_schema(&cache, &config);
11181
11182        // Generate subscription fields
11183        let sub_fields = generate_subscription_fields(&cache, &generated);
11184        assert!(!sub_fields.is_empty(), "Should have subscription fields");
11185
11186        // Build schema with subscriptions
11187        let result = build_dynamic_schema(
11188            &generated,
11189            &cache,
11190            Some(&sub_fields),
11191            None,
11192            Arc::new(Default::default()),
11193            std::time::Duration::from_secs(30),
11194            None,
11195        );
11196        assert!(result.is_ok(), "Schema with subscriptions should build");
11197    }
11198
11199    #[test]
11200    fn test_subscription_field_generation() {
11201        let cache = create_test_schema_cache();
11202        let config = SchemaConfig::default();
11203        let generated = build_schema(&cache, &config);
11204
11205        let fields = generate_subscription_fields(&cache, &generated);
11206
11207        // Should have one subscription field for the users table
11208        assert_eq!(fields.len(), 1);
11209        assert_eq!(fields[0].name, "users");
11210        assert_eq!(fields[0].table_name, "users");
11211        assert_eq!(fields[0].channel_name(), "postrust_public_users");
11212    }
11213
11214    #[test]
11215    fn test_create_subscription_type() {
11216        let cache = create_test_schema_cache();
11217        let config = SchemaConfig::default();
11218        let generated = build_schema(&cache, &config);
11219        // A live query per queryable root: the subscription root mirrors the
11220        // query root, which is the whole contract.
11221        let _subscription = create_subscription_type(
11222            &generated,
11223            "subscription_root",
11224            None,
11225            Arc::new(Default::default()),
11226            Arc::new(Default::default()),
11227            std::time::Duration::from_secs(30),
11228        );
11229    }
11230}
11231
11232/// One step of a JSON path: a key of an object or an index of an array.
11233#[derive(Debug, PartialEq, Eq)]
11234enum PathStep {
11235    Key(String),
11236    Index(usize),
11237}
11238
11239/// Read one part of a document, by the path spelling Hasura accepts.
11240///
11241/// `column(path: "objs[0]['你好']")` -- an optional leading `$`, then keys
11242/// written bare or after a dot, and indices or quoted keys written in
11243/// brackets. A step that names something the document does not have is null,
11244/// which is what `#>` answers to the same path.
11245fn walk_json_path(value: &Value, path: &str) -> Result<Value, async_graphql::Error> {
11246    let mut at = value;
11247    for step in parse_json_path(path)? {
11248        let next = match (&step, at) {
11249            (PathStep::Key(key), Value::Object(map)) => map.get(key.as_str()),
11250            (PathStep::Index(index), Value::List(items)) => items.get(*index),
11251            // An index of an object or a key of an array reads nothing, and so
11252            // does anything asked of a scalar.
11253            _ => None,
11254        };
11255        match next {
11256            Some(next) => at = next,
11257            None => return Ok(Value::Null),
11258        }
11259    }
11260    Ok(at.clone())
11261}
11262
11263/// A path whose bracket never closes: written wrong rather than written oddly.
11264fn unclosed_bracket(path: &str) -> async_graphql::Error {
11265    let message = format!("\"{}\" is not a JSON path: a bracket is never closed", path);
11266    validation_error(&message)
11267}
11268
11269/// Parse the path spelling Hasura accepts into its steps.
11270///
11271/// Deliberately tolerant about separators, because the corpus writes the same
11272/// path four ways: `objs[0]["x"]`, `objs[0].["x"]`, `objs.[0].["x"]` and
11273/// `.objs[0]['x']` all name one thing. What is not tolerated is a bracket that
11274/// never closes, which is a path the client got wrong rather than a spelling.
11275fn parse_json_path(path: &str) -> Result<Vec<PathStep>, async_graphql::Error> {
11276    let mut steps = Vec::new();
11277    let mut chars = path.chars().peekable();
11278    // `$` is the document itself, and naming it is optional.
11279    if chars.peek() == Some(&'$') {
11280        chars.next();
11281    }
11282    while let Some(&c) = chars.peek() {
11283        match c {
11284            // A separator carries no step of its own.
11285            '.' => {
11286                chars.next();
11287            }
11288            '[' => {
11289                chars.next();
11290                let quote = match chars.peek() {
11291                    Some(&q @ ('\'' | '"')) => {
11292                        chars.next();
11293                        Some(q)
11294                    }
11295                    _ => None,
11296                };
11297                let mut inner = String::new();
11298                loop {
11299                    let Some(c) = chars.next() else {
11300                        return Err(unclosed_bracket(path));
11301                    };
11302                    match (quote, c) {
11303                        (Some(quote), c) if c == quote => break,
11304                        (None, ']') => break,
11305                        _ => inner.push(c),
11306                    }
11307                }
11308                // A quoted key's closing bracket, which the quote ended before.
11309                if quote.is_some() {
11310                    match chars.next() {
11311                        Some(']') => {}
11312                        _ => return Err(unclosed_bracket(path)),
11313                    }
11314                }
11315                // An unquoted number is an index; anything else is a key,
11316                // since an object may be keyed by a word as well as a digit.
11317                match (quote, inner.parse::<usize>()) {
11318                    (None, Ok(index)) => steps.push(PathStep::Index(index)),
11319                    _ => steps.push(PathStep::Key(inner)),
11320                }
11321            }
11322            _ => {
11323                let mut key = String::new();
11324                while let Some(&c) = chars.peek() {
11325                    if matches!(c, '.' | '[') {
11326                        break;
11327                    }
11328                    key.push(c);
11329                    chars.next();
11330                }
11331                steps.push(PathStep::Key(key));
11332            }
11333        }
11334    }
11335    Ok(steps)
11336}
11337
11338#[cfg(test)]
11339mod json_path_tests {
11340    use super::*;
11341
11342    fn steps(path: &str) -> Vec<PathStep> {
11343        parse_json_path(path).expect("the path parses")
11344    }
11345
11346    fn key(name: &str) -> PathStep {
11347        PathStep::Key(name.to_string())
11348    }
11349
11350    #[test]
11351    fn the_document_itself_has_no_steps() {
11352        assert_eq!(steps("$"), vec![]);
11353        assert_eq!(steps(""), vec![]);
11354    }
11355
11356    #[test]
11357    fn a_key_is_written_bare_or_after_a_dot() {
11358        assert_eq!(steps("a"), vec![key("a")]);
11359        assert_eq!(steps(".obj.c1"), vec![key("obj"), key("c1")]);
11360        assert_eq!(steps("._underscore"), vec![key("_underscore")]);
11361    }
11362
11363    #[test]
11364    fn a_bracket_holds_an_index_or_a_quoted_key() {
11365        assert_eq!(steps("arr[0]"), vec![key("arr"), PathStep::Index(0)]);
11366        assert_eq!(steps("['!@#$%^']"), vec![key("!@#$%^")]);
11367        assert_eq!(
11368            steps("translations['hello world!']"),
11369            vec![key("translations"), key("hello world!")]
11370        );
11371    }
11372
11373    /// The corpus writes one path four ways, and Hasura reads them all alike.
11374    #[test]
11375    fn a_dot_before_a_bracket_changes_nothing() {
11376        let expected = vec![key("objs"), PathStep::Index(0), key("\u{4f60}\u{597d}")];
11377        for spelling in [
11378            "objs[0]['\u{4f60}\u{597d}']",
11379            "objs[0][\"\u{4f60}\u{597d}\"]",
11380            "objs[0].[\"\u{4f60}\u{597d}\"]",
11381            "objs.[0].[\"\u{4f60}\u{597d}\"]",
11382        ] {
11383            assert_eq!(steps(spelling), expected, "{}", spelling);
11384        }
11385    }
11386
11387    #[test]
11388    fn a_bracket_that_never_closes_is_refused() {
11389        assert!(parse_json_path("arr[0").is_err());
11390        assert!(parse_json_path("['key'").is_err());
11391    }
11392
11393    #[test]
11394    fn a_step_the_document_does_not_have_reads_null() {
11395        let document = Value::from_json(serde_json::json!({
11396            "obj": {"c1": "c2"},
11397            "arr": [1, 2, 3],
11398        }))
11399        .expect("a document");
11400        let at = |path| walk_json_path(&document, path).expect("the path parses");
11401        assert_eq!(at(".obj.c1"), Value::String("c2".to_string()));
11402        assert_eq!(at("arr[1]"), Value::Number(2.into()));
11403        assert_eq!(at("arr[9]"), Value::Null);
11404        assert_eq!(at(".obj.nothing"), Value::Null);
11405        // An index of an object, and a key of an array: neither reads
11406        // anything, which is what `#>` answers.
11407        assert_eq!(at("obj[0]"), Value::Null);
11408        assert_eq!(at("arr.first"), Value::Null);
11409    }
11410}