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    /// Serve subscriptions on this state from `previous`'s broker, listening
391    /// on whatever channels this schema adds.
392    ///
393    /// For a schema rebuilt under a running server. The broker carries over
394    /// rather than being replaced, so a subscription opened on the previous
395    /// schema keeps receiving notifications. With no broker yet -- start-up
396    /// could not connect one, or there was no previous state -- one is
397    /// started as `init_subscriptions` would.
398    pub async fn continue_subscriptions(
399        &mut self,
400        previous: Option<&GraphQLState>,
401    ) -> Result<(), crate::subscription::BrokerError> {
402        if let Some(previous) = previous {
403            self.broker = Arc::clone(&previous.broker);
404        }
405        if !self.config.enable_subscriptions {
406            return Ok(());
407        }
408        if let Some(broker) = self.broker.read().await.as_ref() {
409            let channels = self
410                .subscription_fields
411                .iter()
412                .map(|f| f.channel_name())
413                .collect();
414            return broker.listen(channels).await;
415        }
416        self.init_subscriptions().await
417    }
418
419    /// Get the notification broker.
420    pub async fn get_broker(&self) -> Option<Arc<RwLock<Option<NotifyBroker>>>> {
421        Some(Arc::clone(&self.broker))
422    }
423}
424
425/// Handle a GraphQL request.
426pub async fn graphql_handler(
427    State(state): State<Arc<GraphQLState>>,
428    ctx: GraphQLContext,
429    req: GraphQLRequest,
430) -> GraphQLResponse {
431    let request = req
432        .into_inner()
433        .data(ctx)
434        .data(state.pool.clone())
435        .data(Arc::clone(&state.broker));
436    state.schema.execute(request).await.into()
437}
438
439/// Handle GraphQL WebSocket subscription upgrade.
440///
441/// This should be called with a WebSocket upgrade request to enable
442/// GraphQL subscriptions over WebSocket.
443///
444/// It authenticates nothing: no caller identity, session or JWT claims reach
445/// the operations it runs. `postrust-server` serves its WebSocket routes with
446/// its own handler, which settles the identity from `connection_init` exactly
447/// as an HTTP request's is settled; an embedder needing the same must insert a
448/// [`GraphQLContext`](crate::context::GraphQLContext) per operation itself.
449pub async fn graphql_ws_handler(
450    State(state): State<Arc<GraphQLState>>,
451    protocol: async_graphql_axum::GraphQLProtocol,
452    ws: axum::extract::WebSocketUpgrade,
453) -> impl IntoResponse {
454    let schema = state.schema.clone();
455    let pool = state.pool.clone();
456    let broker = Arc::clone(&state.broker);
457
458    ws.protocols(["graphql-transport-ws", "graphql-ws"])
459        .on_upgrade(move |socket| async move {
460            let mut data = async_graphql::Data::default();
461            data.insert(pool);
462            data.insert(broker);
463
464            async_graphql_axum::GraphQLWebSocket::new(socket, schema, protocol)
465                .with_data(data)
466                .serve()
467                .await
468        })
469}
470
471/// Handle GraphQL playground request.
472pub async fn graphql_playground() -> impl axum::response::IntoResponse {
473    axum::response::Html(async_graphql::http::playground_source(
474        async_graphql::http::GraphQLPlaygroundConfig::new("/graphql")
475            .subscription_endpoint("/graphql/ws"),
476    ))
477}
478
479/// The column a field name refers to.
480///
481/// The name itself, unless the table exposes that column under another one.
482/// Every path from a request to SQL goes through here: a renamed column is a
483/// name in the schema and in nothing else, so the translation has to happen at
484/// each boundary rather than once.
485fn column_for<'a>(
486    names: &'a crate::names::NameOverrides,
487    schema: &str,
488    table: &str,
489    field: &'a str,
490) -> &'a str {
491    names.column_source(schema, table, field).unwrap_or(field)
492}
493
494/// The same, given the table itself.
495fn table_column_for<'a>(
496    names: &'a crate::names::NameOverrides,
497    table: &postrust_core::schema_cache::Table,
498    field: &'a str,
499) -> &'a str {
500    column_for(names, &table.schema, &table.name, field)
501}
502
503/// The select list that renames a table's columns to the fields they are
504/// exposed as, or `None` where nothing is renamed.
505///
506/// Applied to a projection over the table rather than to the table itself, so
507/// the row a computed field or a computed relationship is passed stays the
508/// table's own composite -- `cannot cast type record to author` is what
509/// happens when it does not.
510fn rename_projection(
511    table: &postrust_core::schema_cache::Table,
512    alias: &str,
513    names: &crate::names::NameOverrides,
514) -> Option<String> {
515    if !names.renames_columns(&table.schema, &table.name) {
516        return None;
517    }
518    let parts: Vec<String> = table
519        .columns
520        .values()
521        .map(|column| {
522            let exposed = names
523                .column(&table.schema, &table.name, &column.name)
524                .unwrap_or(&column.name);
525            format!(
526                "{}.{} AS {}",
527                postrust_sql::escape_ident(alias),
528                postrust_sql::escape_ident(&column.name),
529                postrust_sql::escape_ident(exposed)
530            )
531        })
532        .collect();
533    match parts.is_empty() {
534        true => None,
535        false => Some(parts.join(", ")),
536    }
537}
538
539/// A table's column types, keyed by the names those columns are exposed under.
540///
541/// [`row_json`] names the shape columns it has to rewrite as GeoJSON, and it
542/// names them in whatever the projection called them. Over a renamed
543/// projection that is the field name, not the column's.
544fn exposed_column_types(
545    table: &postrust_core::schema_cache::Table,
546    names: &crate::names::NameOverrides,
547) -> HashMap<String, String> {
548    table
549        .columns
550        .values()
551        .map(|column| {
552            let exposed = names
553                .column(&table.schema, &table.name, &column.name)
554                .unwrap_or(&column.name);
555            (exposed.to_string(), column.nominal_type.clone())
556        })
557        .collect()
558}
559
560/// The type a value is written as, which keeps the list an array column is.
561///
562/// Not the leaf scalar: `c34_text_array` is a `text[]`, and a client sending
563/// `$textArray: [String]` into it was refused for offering a list where the
564/// input said `String`. Every write is optional, so nothing here is non-null.
565fn write_type_ref(graphql_type: &crate::types::GraphQLType) -> TypeRef {
566    match graphql_type {
567        crate::types::GraphQLType::List(inner) => TypeRef::named_list(leaf_scalar_name(inner)),
568        other => TypeRef::named(leaf_scalar_name(other)),
569    }
570}
571
572/// The scalar at the bottom of a type, past any list wrapping.
573fn leaf_scalar_name(graphql_type: &crate::types::GraphQLType) -> String {
574    match graphql_type {
575        crate::types::GraphQLType::List(inner) => leaf_scalar_name(inner),
576        other => other.to_string(),
577    }
578}
579
580/// Build the dynamic async-graphql schema from our generated schema.
581fn build_dynamic_schema(
582    generated: &GeneratedSchema,
583    _schema_cache: &SchemaCache,
584    subscription_fields: Option<&[SubField]>,
585    max_rows: Option<i64>,
586    names: Arc<crate::names::NameOverrides>,
587    subscription_refresh: std::time::Duration,
588    // Whose schema this is, where it is one role's. What it decides here is
589    // which columns a write may name, which is not the same question as which
590    // columns may be read -- Hasura grants the two separately, and a role that
591    // may set a column it cannot read is ordinary rather than exotic.
592    role: Option<&str>,
593) -> Result<Schema, GraphQLError> {
594    // Create object types for each table
595    let mut object_types: HashMap<String, Object> = HashMap::new();
596
597    // A table with no readable field gets no type at all. That is not an
598    // empty table -- it is one this role may write and not read, and a GraphQL
599    // object type with no fields is not a legal type. Everything that would
600    // have returned it is left out with it.
601    for (type_name, obj) in &generated.object_types {
602        if obj.fields.is_empty() {
603            continue;
604        }
605        let relationships = generated
606            .relationship_fields
607            .get(type_name)
608            .map(|r| r.as_slice())
609            .unwrap_or(&[]);
610        let table_obj = create_object_type(
611            obj,
612            relationships,
613            generated.federation_entities.get(type_name),
614        );
615        object_types.insert(type_name.clone(), table_obj);
616    }
617
618    // Aggregate types: every readable table gets them, because every table has
619    // a count even when it has nothing to sum -- and so does a table a role may
620    // count and not read, which is the whole of what such a role was granted.
621    // Read from the roots that were generated rather than worked out again,
622    // for the reason the write inputs are: two answers to one question drift.
623    let countable: HashSet<&str> = generated
624        .query_fields
625        .iter()
626        .filter(|field| field.aggregates && !field.is_by_pk)
627        .map(|field| field.type_name.as_str())
628        .collect();
629    for (type_name, obj) in &generated.object_types {
630        if obj.fields.is_empty() && !countable.contains(type_name.as_str()) {
631            continue;
632        }
633        for aggregate_type in create_aggregate_types(&obj.local_name, obj) {
634            object_types.insert(aggregate_type.type_name().to_string(), aggregate_type);
635        }
636    }
637
638    // One mutation response per table that has any mutation, and only those:
639    // an unreferenced type is still a type, and a read-only view would
640    // otherwise contribute a response nothing can return.
641    let mutable: HashMap<String, String> = generated
642        .mutation_fields
643        .iter()
644        .filter_map(|f| {
645            // The table's own name. A bulk write already answers with the
646            // response type, and trimming the suffix here is what keeps
647            // `<t>_mutation_response_mutation_response` from being built: the
648            // set used to be carried by `insert_one` and `update_by_pk`
649            // naming the bare type, so a table with only bulk writes -- which
650            // is what a role that cannot read one has -- registered the wrong
651            // name and left the right one missing.
652            let response = f
653                .return_type
654                .trim_matches(|c| c == '[' || c == ']' || c == '!')
655                .strip_suffix("_mutation_response")?;
656            Some((response.to_string(), f.type_name.clone()))
657        })
658        .collect();
659    for (base_name, row_type_name) in mutable {
660        // Whether it has rows to give back. A write to a table this role
661        // cannot read answers with `affected_rows` and nothing else -- there
662        // is no row type for `returning` to be a list of.
663        let returning = generated
664            .object_types
665            .get(&row_type_name)
666            .is_some_and(|object| !object.fields.is_empty());
667        object_types.insert(
668            mutation_response_type_name(&base_name),
669            create_mutation_response_type(&base_name, &row_type_name, returning),
670        );
671    }
672
673    // Non-federated Hasura-compatible schemas use `query_root`, not `Query`.
674    // Federation SDL from async-graphql does not include an operation-root
675    // schema definition, so Apollo composition treats only the conventional
676    // root names as root types.
677    let query_root = if generated.enable_federation {
678        "Query"
679    } else {
680        "query_root"
681    };
682    let mutation_root = if generated.enable_federation {
683        "Mutation"
684    } else {
685        "mutation_root"
686    };
687    let subscription_root = if generated.enable_federation {
688        "Subscription"
689    } else {
690        "subscription_root"
691    };
692
693    // Create query type. Resolvers need the relationship map to embed related
694    // rows, so it is shared into each closure.
695    let relationships = Arc::new(generated.relationship_fields.clone());
696    // (schema, table) -> GraphQL type name. A nested insert is handed a table
697    // by a relationship and has to find that table's relationships in turn,
698    // and the map they live in is keyed by type name -- which is not always
699    // the table's name, since a second schema prefixes it and a name may have
700    // been given.
701    let type_names: Arc<HashMap<(String, String), String>> = Arc::new(
702        generated
703            .object_types
704            .iter()
705            .map(|(type_name, object)| {
706                (
707                    (object.table.schema.clone(), object.table.name.clone()),
708                    type_name.clone(),
709                )
710            })
711            .collect(),
712    );
713    let query = add_function_fields(
714        create_query_type(
715            generated,
716            query_root,
717            max_rows,
718            Arc::clone(&relationships),
719            Arc::clone(&names),
720        ),
721        generated,
722        false,
723        max_rows,
724        Arc::clone(&relationships),
725        Arc::clone(&names),
726    );
727
728    // Create mutation type
729    let mutation = if !generated.mutation_fields.is_empty()
730        || generated.function_fields.iter().any(|f| f.volatile)
731    {
732        Some(add_function_fields(
733            create_mutation_type(
734                generated,
735                mutation_root,
736                Arc::clone(&relationships),
737                Arc::clone(&type_names),
738                Arc::clone(&names),
739                max_rows,
740                role,
741            ),
742            generated,
743            true,
744            max_rows,
745            Arc::clone(&relationships),
746            Arc::clone(&names),
747        ))
748    } else {
749        None
750    };
751
752    // Create subscription type if enabled
753    let subscription = subscription_fields.map(|_| {
754        create_subscription_type(
755            generated,
756            subscription_root,
757            max_rows,
758            Arc::clone(&relationships),
759            Arc::clone(&names),
760            subscription_refresh,
761        )
762    });
763
764    // Build schema
765    let mut builder = Schema::build(
766        query_root,
767        mutation.as_ref().map(|_| mutation_root),
768        subscription.as_ref().map(|_| subscription_root),
769    );
770    if generated.enable_federation {
771        builder = builder
772            .enable_federation()
773            .extension(NullableFederationEntity);
774        let entity_state = Arc::new(EntityResolverState::new(
775            generated,
776            Arc::clone(&relationships),
777            Arc::clone(&names),
778            max_rows,
779        ));
780        builder = builder.entity_resolver(move |ctx| {
781            let state = Arc::clone(&entity_state);
782            FieldFuture::new(async move { resolve_entities(&ctx, state.as_ref()).await })
783        });
784    }
785
786    // Register all object types
787    for (_, obj) in object_types {
788        builder = builder.register(obj);
789    }
790
791    // Register query type
792    builder = builder.register(query);
793
794    // Register mutation type if present
795    if let Some(mutation) = mutation {
796        builder = builder.register(mutation);
797    }
798
799    // Register subscription type if present
800    if let Some(subscription) = subscription {
801        builder = builder.register(subscription);
802    }
803
804    // Register scalar types
805    // Every scalar the generated types actually name, rather than a fixed
806    // list. A `geometry` column, a `raster` column and a database enum each
807    // become a scalar under their own PostgreSQL name, because that is the
808    // name a client's query declares its variables with -- and a scalar the
809    // schema mentions but never registers makes the whole schema unbuildable.
810    let (bool_exp_inputs, bool_exp_scalars) = crate::input::bool_exp::build_inputs(
811        &generated.object_types,
812        &generated.relationship_fields,
813    );
814    for input in bool_exp_inputs {
815        builder = builder.register(input);
816    }
817
818    let mut scalar_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
819    for object in generated.object_types.values() {
820        // Both halves. A column a role may set without seeing is named by the
821        // insert input and by nothing a read produces, so collecting only the
822        // readable fields leaves its scalar unregistered -- and a scalar the
823        // schema mentions but never registers makes the whole schema
824        // unbuildable, which costs the role every field it has.
825        for field in object.fields.iter().chain(&object.writable_fields) {
826            scalar_names.insert(leaf_scalar_name(&field.graphql_type));
827        }
828    }
829    // Used as an argument type in its own right, whether or not any column is
830    // one: `objects`, `_set` and the mutation inputs are still JSON.
831    scalar_names.insert("JSON".to_string());
832    // A function's arguments name scalars of their own: a table may have no
833    // geometry column while a function takes one.
834    for function in &generated.function_fields {
835        for (_, pg_type, _) in &function.arguments {
836            scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
837        }
838    }
839    // The same for the functions behind computed fields, whose arguments are
840    // written where the field is asked for rather than at a root.
841    for object in generated.object_types.values() {
842        for field in &object.fields {
843            for (_, pg_type, _) in &field.arguments {
844                scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
845            }
846        }
847    }
848    for relationships in generated.relationship_fields.values() {
849        for relationship in relationships {
850            for (_, pg_type, _) in &relationship.arguments {
851                scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
852            }
853        }
854    }
855
856    // Every scalar the boolean expressions name, which is more than the
857    // scalars the columns are: a cast from a geometry names `geography`, and a
858    // raster comparison names `geometry`. Taken from what was generated rather
859    // than listed here, because a list here is a second place to keep in step
860    // and it fell out of step twice.
861    scalar_names.extend(bool_exp_scalars);
862
863    for name in scalar_names {
864        if matches!(name.as_str(), "Int" | "Float" | "String" | "Boolean" | "ID") {
865            continue;
866        }
867        // An enum table's type is an enum, not a scalar. It reaches this list
868        // because a column typed as one names it, and registering both would
869        // define the same type twice.
870        if generated.enum_types.contains_key(&name) {
871            continue;
872        }
873        builder = builder
874            .register(Scalar::new(&name).description(format!("The PostgreSQL `{}` type.", name)));
875    }
876
877    // Register the boolean expression inputs -- one per table, plus one
878    // comparison input per scalar the tables use.
879
880    // A key as one object, for `update_x_by_pk(pk_columns: {...})`. Only the
881    // by-key update spells its key this way; the by-key query and delete take
882    // the columns as arguments, and both spellings are what a generated client
883    // already sends.
884    let mut key_inputs: HashMap<String, InputObject> = HashMap::new();
885    for field in &generated.mutation_fields {
886        if field.mutation_type != MutationType::UpdateByPk || field.pk_columns.is_empty() {
887            continue;
888        }
889        let base_name = field.local_type_name.as_str();
890        let type_name = format!("{}_pk_columns_input", base_name);
891        let mut input = InputObject::new(&type_name)
892            .description(format!("The primary key of one {} row.", base_name));
893        for (column, pg_type) in &field.pk_columns {
894            input = input.field(InputValue::new(
895                names
896                    .column(&field.schema_name, &field.table_name, column)
897                    .unwrap_or(column),
898                TypeRef::named_nn(exposed_pk_argument_type(
899                    names.as_ref(),
900                    &field.schema_name,
901                    &field.table_name,
902                    column,
903                    pg_type,
904                )),
905            ));
906        }
907        key_inputs.insert(type_name, input);
908    }
909    for (_, input) in key_inputs {
910        builder = builder.register(input);
911    }
912
913    // What a mutation writes. `objects` and `_set` were `JSON`, which accepts
914    // anything and lets a client generate nothing: no completion, no codegen,
915    // and a misspelled column reaching the database instead of being caught
916    // before the request was sent. The same argument that made `where` a real
917    // type applies unchanged.
918    // Which types have an `_insert_input` at all, which is what a nested write
919    // names. Collected first because a relationship may point at a table this
920    // loop has not reached yet -- and because the answer is now a permission
921    // question: a role granted `insert` on `article` and only `select` on
922    // `author` gets an `article_insert_input`, and writing `author` inside it
923    // would name a type nobody registered. async-graphql refuses to build a
924    // schema with a dangling reference, so the whole role would have no schema
925    // over one field it was never allowed to use.
926    // Which types actually got each write input, recorded as they are built
927    // rather than predicted from the object type's fields. The two answers
928    // used to be the same and are not any more: an input is built from the
929    // write permission's columns, so a table whose readable columns include a
930    // number may still have no `_inc_input` -- and an argument naming a type
931    // nobody registered is a schema async-graphql refuses to build, which
932    // costs the role every field it has rather than the one it cannot use.
933    let insertable_types: HashSet<&str> = generated
934        .object_types
935        .iter()
936        .filter(|(_, object)| crate::input::mutation::is_insertable(&object.table))
937        .map(|(type_name, _)| type_name.as_str())
938        .collect();
939
940    for (type_name, object) in &generated.object_types {
941        let local_type_name = object.local_name.as_str();
942        let table = &object.table;
943        // Which columns a write may name, which is the write permission's
944        // answer and not the read permission's. A role granted `columns:
945        // ["age"]` on insert may not name `is_user` even where it can read it,
946        // and the corpus tests exactly that: `field 'is_user' not found in
947        // type: 'resident_insert_input'`.
948        // Only real columns are written. A computed field is a function of the
949        // row and a relationship is handled separately below.
950        let writable_named = |allowed: Option<&crate::names::ColumnSet>| {
951            object
952                .writable_fields
953                .iter()
954                .filter(|field| {
955                    let column = table_column_for(&names, table, &field.name);
956                    table.get_column(column).is_some()
957                        && allowed.is_none_or(|set| set.allows(column))
958                })
959                .collect::<Vec<&crate::schema::object::GraphQLField>>()
960        };
961        let writable = writable_named(None);
962        let insert_fields = write_fields(object, &names, role, crate::role::Verb::Insert);
963        let update_fields = write_fields(object, &names, role, crate::role::Verb::Update);
964        if writable.is_empty() {
965            continue;
966        }
967
968        let relationships = generated
969            .relationship_fields
970            .get(type_name)
971            .map(|r| r.as_slice())
972            .unwrap_or(&[]);
973        let conflict_type = match table.unique_constraints.is_empty() {
974            true => None,
975            false => Some(format!("{}_on_conflict", local_type_name)),
976        };
977
978        if crate::input::mutation::is_insertable(table) {
979            let mut insert = InputObject::new(format!("{}_insert_input", local_type_name))
980                .description(format!("The columns of a new {} row.", local_type_name));
981            let mut taken: HashSet<&str> = HashSet::new();
982            for field in &insert_fields {
983                // Every column optional: which ones the database insists on is
984                // the database's answer, and a column that is NOT NULL with a
985                // default does not have to be given.
986                taken.insert(field.name.as_str());
987                insert = insert.field(InputValue::new(
988                    &field.name,
989                    write_type_ref(&field.graphql_type),
990                ));
991            }
992            // A nested write: the rows to insert beside this one. Only where
993            // the far side is something this schema can write -- see
994            // `insertable_types`.
995            for relationship in relationships {
996                if !insertable_types.contains(relationship.target_type.as_str()) {
997                    continue;
998                }
999                if !taken.insert(relationship.name.as_str()) {
1000                    continue;
1001                }
1002                let suffix = match relationship.is_list {
1003                    true => "arr_rel_insert_input",
1004                    false => "obj_rel_insert_input",
1005                };
1006                insert = insert.field(InputValue::new(
1007                    &relationship.name,
1008                    TypeRef::named(format!("{}_{}", relationship.target_local_name, suffix)),
1009                ));
1010            }
1011            builder = builder.register(insert);
1012
1013            // How this table is written as somebody else's nested row. Both
1014            // shapes carry an `on_conflict`, which is what makes a nested
1015            // upsert expressible.
1016            let data_type = format!("{}_insert_input", local_type_name);
1017            let mut object_rel =
1018                InputObject::new(format!("{}_obj_rel_insert_input", local_type_name))
1019                    .description(format!(
1020                        "One {} row written beside its parent.",
1021                        local_type_name
1022                    ))
1023                    .field(InputValue::new("data", TypeRef::named_nn(&data_type)));
1024            let mut array_rel =
1025                InputObject::new(format!("{}_arr_rel_insert_input", local_type_name))
1026                    .description(format!(
1027                        "{} rows written beside their parent.",
1028                        local_type_name
1029                    ))
1030                    .field(InputValue::new(
1031                        "data",
1032                        TypeRef::named_nn_list_nn(&data_type),
1033                    ));
1034            if let Some(conflict) = &conflict_type {
1035                object_rel =
1036                    object_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1037                array_rel =
1038                    array_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1039            }
1040            builder = builder.register(object_rel).register(array_rel);
1041        }
1042
1043        if crate::input::mutation::is_updatable(table) {
1044            let mut set = InputObject::new(format!("{}_set_input", local_type_name))
1045                .description(format!("Columns of {} to replace.", local_type_name));
1046            let mut numeric = InputObject::new(format!("{}_inc_input", local_type_name))
1047                .description(format!("Columns of {} to add to.", local_type_name));
1048            let mut any_numeric = false;
1049            let mut any_jsonb = false;
1050            for field in &update_fields {
1051                if matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1052                    any_jsonb = true;
1053                }
1054                set = set.field(InputValue::new(
1055                    &field.name,
1056                    write_type_ref(&field.graphql_type),
1057                ));
1058                // Only a number can be added to, and a list of them is not a
1059                // number: `_inc` takes the scalar or nothing.
1060                if crate::schema::aggregate::is_incrementable(&field.graphql_type) {
1061                    any_numeric = true;
1062                    numeric = numeric.field(InputValue::new(
1063                        &field.name,
1064                        TypeRef::named(leaf_scalar_name(&field.graphql_type)),
1065                    ));
1066                }
1067            }
1068            builder = builder.register(set);
1069            // A table with nothing to add to gets no type for adding to it.
1070            if any_numeric {
1071                builder = builder.register(numeric);
1072            }
1073
1074            // One entry of `update_x_many`: a filter and the values to write
1075            // where it matches. The whole list runs in one transaction, in the
1076            // order it was given, which is what makes it different from
1077            // sending the updates one at a time.
1078            let mut updates = InputObject::new(format!("{}_updates", local_type_name))
1079                .description(format!(
1080                    "One update to {}: which rows, and what to write.",
1081                    local_type_name
1082                ))
1083                .field(InputValue::new(
1084                    "where",
1085                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1086                ))
1087                .field(InputValue::new(
1088                    "_set",
1089                    TypeRef::named(format!("{}_set_input", local_type_name)),
1090                ));
1091            if any_numeric {
1092                updates = updates.field(InputValue::new(
1093                    "_inc",
1094                    TypeRef::named(format!("{}_inc_input", local_type_name)),
1095                ));
1096            }
1097            // What a document column may be told to do, one input per
1098            // operator: each takes a value of a different shape -- a
1099            // document, a key, an index, a path -- and only the columns that
1100            // hold documents may be told any of it. A table with none is not
1101            // given the operators at all, which is what says in the schema
1102            // that `_append` is not a thing to write there.
1103            for (operator, _) in JSONB_OPERATORS {
1104                if !any_jsonb {
1105                    break;
1106                }
1107                updates = updates.field(InputValue::new(
1108                    *operator,
1109                    TypeRef::named(jsonb_operator_input(local_type_name, operator)),
1110                ));
1111            }
1112            builder = builder.register(updates);
1113            if any_jsonb {
1114                for (operator, item) in JSONB_OPERATORS {
1115                    let mut input =
1116                        InputObject::new(jsonb_operator_input(local_type_name, operator))
1117                            .description(format!(
1118                                "Columns of {} to apply `{}` to.",
1119                                local_type_name, operator
1120                            ));
1121                    for field in &update_fields {
1122                        if !matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1123                            continue;
1124                        }
1125                        input = input.field(InputValue::new(&field.name, item()));
1126                    }
1127                    builder = builder.register(input);
1128                }
1129            }
1130        }
1131    }
1132
1133    // A function's own arguments, under a name of their own so they cannot
1134    // collide with `where` or `limit`.
1135    for function in &generated.function_fields {
1136        if function.arguments.is_empty() {
1137            continue;
1138        }
1139        let mut args = InputObject::new(format!("{}_args", function.name))
1140            .description(format!("Arguments to {}.", function.name));
1141        for (name, pg_type, _) in &function.arguments {
1142            let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1143            // Nullable, whether or not the parameter has a default. Every
1144            // PostgreSQL argument accepts a null, and a client passing one
1145            // through a variable declares it as the nullable type -- `query
1146            // ($point: json)` used where `json!` was expected is a query the
1147            // spec's variable rule refuses. Whether a defaulted argument may
1148            // be left out entirely is enforced where the call is written,
1149            // which is the only place that can tell.
1150            args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1151        }
1152        builder = builder.register(args);
1153    }
1154
1155    // The same for a computed relationship's function, which takes its
1156    // arguments where it is embedded rather than at the root. Named after the
1157    // type and the field, since two tables may reach the same function and one
1158    // table may reach two.
1159    for (type_name, relationships) in &generated.relationship_fields {
1160        let local_type_name = generated
1161            .object_types
1162            .get(type_name)
1163            .map(|object| object.local_name.as_str())
1164            .unwrap_or(type_name);
1165        for relationship in relationships {
1166            if relationship.arguments.is_empty() {
1167                continue;
1168            }
1169            let mut args =
1170                InputObject::new(computed_args_type_name(local_type_name, &relationship.name))
1171                    .description(format!(
1172                        "Arguments to {}, beside the row it is asked of.",
1173                        relationship.name
1174                    ));
1175            for (name, pg_type, _) in &relationship.arguments {
1176                let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1177                // Nullable, for the reason the function arguments above are.
1178                args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1179            }
1180            builder = builder.register(args);
1181        }
1182    }
1183
1184    // And for a computed *column*'s function, which likewise takes its
1185    // arguments where the field is asked for. `locations { distance(args: {
1186    // from: ... }) }` -- the field is a function of the row and of what the
1187    // caller wants measured against it.
1188    for object in generated.object_types.values() {
1189        let local_type_name = object.local_name.as_str();
1190        for field in &object.fields {
1191            if field.arguments.is_empty() {
1192                continue;
1193            }
1194            let mut args = InputObject::new(computed_args_type_name(local_type_name, &field.name))
1195                .description(format!(
1196                    "Arguments to {}, beside the row it is asked of.",
1197                    field.name
1198                ));
1199            for (name, pg_type, _) in &field.arguments {
1200                let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1201                // Nullable, for the reason the function arguments above are.
1202                args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1203            }
1204            builder = builder.register(args);
1205        }
1206    }
1207
1208    // What a predicate over a related set may ask. Registered only for the
1209    // tables something points at with a to-many relationship, which is the
1210    // only place the field naming them exists.
1211    {
1212        use crate::input::bool_exp as be;
1213        let mut targets: BTreeMap<&str, &str> = BTreeMap::new();
1214        for relationships in generated.relationship_fields.values() {
1215            for relationship in relationships {
1216                if relationship.is_list {
1217                    targets.insert(
1218                        relationship.target_local_name.as_str(),
1219                        relationship.target_type.as_str(),
1220                    );
1221                }
1222            }
1223        }
1224        for (target, target_type) in targets {
1225            let Some(object) = generated.object_types.get(target_type) else {
1226                continue;
1227            };
1228            // A boolean column is what `bool_and` and `bool_or` fold, and a
1229            // GraphQL enum may not be empty -- so a table with none gets
1230            // neither the aggregates nor the enum naming their columns.
1231            let booleans: Vec<&str> = object
1232                .fields
1233                .iter()
1234                .filter(|field| {
1235                    object.table.get_column(&field.name).is_some()
1236                        && matches!(field.graphql_type, crate::types::GraphQLType::Boolean)
1237                })
1238                .map(|field| field.name.as_str())
1239                .collect();
1240
1241            let mut over = InputObject::new(be::aggregate_bool_exp_type_name(target)).description(
1242                format!("A question about the whole set of related {} rows.", target),
1243            );
1244            for function in be::AGGREGATE_PREDICATES {
1245                if function != "count" && booleans.is_empty() {
1246                    continue;
1247                }
1248                over = over.field(InputValue::new(
1249                    function,
1250                    TypeRef::named(be::aggregate_bool_exp_function(target, function)),
1251                ));
1252
1253                // `count` may be told which columns to count, as the field
1254                // itself may; `bool_and` folds exactly one.
1255                let (arguments, predicate) = match function {
1256                    "count" => (
1257                        TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1258                            target,
1259                        )),
1260                        be::comparison_type_name("Int"),
1261                    ),
1262                    _ => (
1263                        TypeRef::named_nn(be::aggregate_bool_exp_columns(target, function)),
1264                        be::comparison_type_name("Boolean"),
1265                    ),
1266                };
1267                let mut input = InputObject::new(be::aggregate_bool_exp_function(target, function))
1268                    .description(format!(
1269                        "`{}` over the related {} rows, and what it has to be.",
1270                        function, target
1271                    ))
1272                    .field(InputValue::new("arguments", arguments))
1273                    .field(InputValue::new("distinct", TypeRef::named("Boolean")))
1274                    .field(InputValue::new(
1275                        "filter",
1276                        TypeRef::named(be::bool_exp_type_name(target)),
1277                    ))
1278                    .field(InputValue::new("predicate", TypeRef::named_nn(predicate)));
1279                if function == "count" {
1280                    input = input.description(format!(
1281                        "How many related {} rows there have to be.",
1282                        target
1283                    ));
1284                }
1285                builder = builder.register(input);
1286
1287                if function != "count" {
1288                    let mut columns = Enum::new(be::aggregate_bool_exp_columns(target, function))
1289                        .description(format!("A boolean column of {}.", target));
1290                    for name in &booleans {
1291                        columns = columns.item(EnumItem::new(*name));
1292                    }
1293                    builder = builder.register(columns);
1294                }
1295            }
1296            builder = builder.register(over);
1297        }
1298    }
1299
1300    // Upserts. A table with no unique constraint has no conflict to resolve,
1301    // and a GraphQL enum may not be empty, so it gets none of these types
1302    // rather than an unusable set of them.
1303    for object in generated.object_types.values() {
1304        let local_type_name = object.local_name.as_str();
1305        // What an upsert may write, which is the update permission's answer
1306        // and not the read permission's: `update_columns` names columns to
1307        // overwrite, so a role that may set a column without seeing it may
1308        // name it here. Where no update permission narrows them these are the
1309        // table's own columns, which is what the enum listed before.
1310        let updatable_columns = write_fields(object, &names, role, crate::role::Verb::Update);
1311        if object.table.unique_constraints.is_empty() {
1312            continue;
1313        }
1314
1315        let mut constraints =
1316            Enum::new(format!("{}_constraint", local_type_name)).description(format!(
1317                "A uniqueness of {} that an insert may conflict with.",
1318                local_type_name
1319            ));
1320        for (name, columns) in &object.table.unique_constraints {
1321            constraints = constraints
1322                .item(EnumItem::new(name).description(format!("unique ({})", columns.join(", "))));
1323        }
1324
1325        let mut updatable = Enum::new(format!("{}_update_column", local_type_name)).description(
1326            format!("A column of {} an upsert may write.", local_type_name),
1327        );
1328        for field in &updatable_columns {
1329            updatable = updatable.item(EnumItem::new(&field.name));
1330        }
1331        // An update permission granting no column leaves the enum with no
1332        // members, which is not a legal type -- and dropping the enum would
1333        // drop `on_conflict` with it, so an insert that names one would fail
1334        // to build rather than be refused. Hasura's answer is a member that
1335        // names no column: the upsert is still expressible, `update_columns:
1336        // []` still means `DO NOTHING`, and naming the placeholder is refused
1337        // where it is used. `article_on_conflict_restricted_role.yaml` tests
1338        // both halves.
1339        if updatable_columns.is_empty() {
1340            updatable = updatable.item(
1341                EnumItem::new(PLACEHOLDER_COLUMN)
1342                    .description("No column may be written by this upsert."),
1343            );
1344        }
1345
1346        let on_conflict = InputObject::new(format!("{}_on_conflict", local_type_name))
1347            .description(format!(
1348                "What to do when an insert into {} conflicts.",
1349                local_type_name
1350            ))
1351            .field(InputValue::new(
1352                "constraint",
1353                TypeRef::named_nn(format!("{}_constraint", local_type_name)),
1354            ))
1355            // An empty list is `DO NOTHING`, which is how Hasura spells "leave
1356            // the row that is already there alone" -- and is the default, so
1357            // `on_conflict: {constraint: article_pkey}` means exactly that
1358            // rather than being refused for saying nothing about columns.
1359            .field(
1360                InputValue::new(
1361                    "update_columns",
1362                    TypeRef::named_nn_list_nn(format!("{}_update_column", local_type_name)),
1363                )
1364                .default_value(Value::List(Vec::new())),
1365            )
1366            .field(InputValue::new(
1367                "where",
1368                TypeRef::named(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1369            ));
1370
1371        builder = builder
1372            .register(constraints)
1373            .register(updatable)
1374            .register(on_conflict);
1375    }
1376
1377    // The enums generated from enum tables.
1378    for (type_name, members) in &generated.enum_types {
1379        let mut generated_enum =
1380            Enum::new(type_name).description(format!("The values {} allows.", type_name));
1381        for (value, comment) in members {
1382            let item = EnumItem::new(value);
1383            generated_enum = generated_enum.item(match comment {
1384                Some(description) => item.description(description),
1385                None => item,
1386            });
1387        }
1388        builder = builder.register(generated_enum);
1389    }
1390
1391    // Ordering: one input per table, one column enum per table, and the single
1392    // direction enum they all share.
1393    let (order_inputs, order_enums) = crate::input::order_by::build_inputs(
1394        &generated.object_types,
1395        &generated.relationship_fields,
1396    );
1397    for input in order_inputs {
1398        builder = builder.register(input);
1399    }
1400    for enum_type in order_enums {
1401        builder = builder.register(enum_type);
1402    }
1403
1404    builder
1405        .finish()
1406        .map_err(|e| GraphQLError::SchemaError(e.to_string()))
1407}
1408
1409/// Create an object type from a TableObjectType.
1410/// The enum member that stands in for an update permission granting no column.
1411///
1412/// Hasura's spelling, and it is load-bearing: `article_update_column` must be
1413/// a legal enum for `article_on_conflict` to exist, and `on_conflict` must
1414/// exist for an insert that carries one to be refused rather than unbuildable.
1415const PLACEHOLDER_COLUMN: &str = "_PLACEHOLDER";
1416
1417/// The name of a table's mutation response type.
1418pub fn mutation_response_type_name(base_name: &str) -> String {
1419    format!("{}_mutation_response", base_name)
1420}
1421
1422/// Build the `<table>_mutation_response` object.
1423///
1424/// Every bulk mutation answers with this rather than with the rows directly.
1425/// `affected_rows` is the count PostgreSQL reports, which is not the length of
1426/// `returning`: a client may ask for no rows back at all and still need to know
1427/// how many were touched, and that is the usual case for a delete.
1428fn create_mutation_response_type(base_name: &str, row_type: &str, returning: bool) -> Object {
1429    let response_name = mutation_response_type_name(base_name);
1430
1431    let response = Object::new(&response_name)
1432        .description(format!("The rows {} changed, and how many.", base_name))
1433        .field(Field::new(
1434            "affected_rows",
1435            TypeRef::named_nn(TypeRef::INT),
1436            |ctx| {
1437                FieldFuture::new(async move {
1438                    if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1439                        if let Some(value) = map.get(&async_graphql::Name::new("affected_rows")) {
1440                            return Ok(Some(FieldValue::value(value.clone())));
1441                        }
1442                    }
1443                    Ok(Some(FieldValue::value(Value::from(0))))
1444                })
1445            },
1446        ));
1447    // A table this role may write and not read has no row type, so there is
1448    // nothing for `returning` to be a list of. Hasura answers such a write
1449    // with the count alone, and the corpus asks for nothing else.
1450    if !returning {
1451        return response;
1452    }
1453    response.field(Field::new(
1454        "returning",
1455        TypeRef::named_nn_list_nn(row_type.to_string()),
1456        |ctx| {
1457            FieldFuture::new(async move {
1458                let rows = match ctx.parent_value.as_value() {
1459                    Some(Value::Object(map)) => {
1460                        match map.get(&async_graphql::Name::new("returning")) {
1461                            Some(Value::List(items)) => items.clone(),
1462                            _ => Vec::new(),
1463                        }
1464                    }
1465                    _ => Vec::new(),
1466                };
1467                Ok(Some(FieldValue::list(
1468                    rows.into_iter().map(FieldValue::value),
1469                )))
1470            })
1471        },
1472    ))
1473}
1474
1475/// Build every aggregate type for one table.
1476///
1477/// Four kinds, and they nest: `<t>_aggregate` holds `aggregate` and `nodes`,
1478/// `<t>_aggregate_fields` holds `count` and one field per function, and each
1479/// function has a type of its own holding the columns it applies to.
1480fn create_aggregate_types(base_name: &str, object: &TableObjectType) -> Vec<Object> {
1481    use crate::schema::aggregate as agg;
1482
1483    let mut types = Vec::new();
1484
1485    // `<t>_aggregate`: the rows, and the numbers about them.
1486    let fields_type = agg::aggregate_fields_type_name(base_name);
1487    // Whether there are rows to hand back beside the numbers. A role granted
1488    // "how many" and not "which" has no row type, so `nodes` has nothing to be
1489    // a list of.
1490    let has_rows = !object.fields.is_empty();
1491    let mut over = Object::new(agg::aggregate_type_name(base_name))
1492        .description(match has_rows {
1493            true => format!("Aggregates over {}, with the rows themselves.", base_name),
1494            false => format!("Aggregates over {}.", base_name),
1495        })
1496        .field(Field::new(
1497            "aggregate",
1498            TypeRef::named(&fields_type),
1499            |ctx| FieldFuture::new(async move { Ok(child_of(&ctx, "aggregate")) }),
1500        ));
1501    if has_rows {
1502        over = over.field(Field::new(
1503            "nodes",
1504            TypeRef::named_nn_list_nn(object.name.clone()),
1505            |ctx| {
1506                FieldFuture::new(async move {
1507                    let rows = match child_value(&ctx, "nodes") {
1508                        Some(Value::List(items)) => items,
1509                        _ => Vec::new(),
1510                    };
1511                    Ok(Some(FieldValue::list(
1512                        rows.into_iter().map(FieldValue::value),
1513                    )))
1514                })
1515            },
1516        ));
1517    }
1518    types.push(over);
1519
1520    // `<t>_aggregate_fields`: count, and one field per function.
1521    let mut count = Field::new("count", TypeRef::named_nn(TypeRef::INT), |ctx| {
1522        // Read under the name it was asked for, not under `count`.
1523        // Two counts of different things sit in one selection --
1524        // `count` beside `distinct_authors: count(columns: [author_id],
1525        // distinct: true)` -- and they are different numbers.
1526        let key = ctx.ctx.field().alias().unwrap_or("count").to_string();
1527        FieldFuture::new(async move {
1528            Ok(Some(FieldValue::value(
1529                child_value(&ctx, &key).unwrap_or(Value::from(0)),
1530            )))
1531        })
1532    });
1533    // `count(columns:)` counts the rows where those columns are not null, and
1534    // `distinct` counts distinct values among them. Neither is offered where
1535    // the role may name no column: `count` is then a count of rows and nothing
1536    // else, and the corpus tests exactly that -- `'count' has no argument
1537    // named 'columns'`.
1538    if has_rows {
1539        count = count
1540            .argument(InputValue::new(
1541                "columns",
1542                TypeRef::named_nn_list(agg_select_column(base_name)),
1543            ))
1544            .argument(InputValue::new("distinct", TypeRef::named("Boolean")));
1545    }
1546    let mut aggregate_fields = Object::new(&fields_type)
1547        .description(format!("Aggregate functions over {}.", base_name))
1548        .field(count);
1549
1550    for (function, returns, columns) in agg::functions_for(object) {
1551        let function_type = agg::function_fields_type_name(base_name, function);
1552        let mut per_column = Object::new(&function_type).description(format!(
1553            "`{}` of each {} column it applies to.",
1554            function, base_name
1555        ));
1556        for column in &columns {
1557            let column_name = column.clone();
1558            let type_name = agg::field_type_for(object, column, returns);
1559            per_column =
1560                per_column.field(Field::new(column, TypeRef::named(type_name), move |ctx| {
1561                    let column_name = column_name.clone();
1562                    FieldFuture::new(async move {
1563                        Ok(child_value(&ctx, &column_name).map(FieldValue::value))
1564                    })
1565                }));
1566        }
1567        types.push(per_column);
1568
1569        let owned = function.to_string();
1570        aggregate_fields = aggregate_fields.field(Field::new(
1571            function,
1572            TypeRef::named(&function_type),
1573            move |ctx| {
1574                let owned = owned.clone();
1575                FieldFuture::new(async move { Ok(child_of(&ctx, &owned)) })
1576            },
1577        ));
1578    }
1579
1580    types.push(aggregate_fields);
1581    types
1582}
1583
1584/// The column enum a `count(columns:)` draws from.
1585fn agg_select_column(base_name: &str) -> String {
1586    crate::input::order_by::select_column_type_name(base_name)
1587}
1588
1589/// One key of the parent object, as a value.
1590fn child_value(ctx: &ResolverContext<'_>, key: &str) -> Option<Value> {
1591    match ctx.parent_value.as_value() {
1592        Some(Value::Object(map)) => map.get(&async_graphql::Name::new(key)).cloned(),
1593        _ => None,
1594    }
1595}
1596
1597/// One key of the parent object, as a resolvable child.
1598///
1599/// `None` where the key is absent or null, which is what a client that did not
1600/// ask for that aggregate should see.
1601fn child_of<'a>(ctx: &ResolverContext<'_>, key: &str) -> Option<FieldValue<'a>> {
1602    match child_value(ctx, key) {
1603        None | Some(Value::Null) => None,
1604        Some(value) => Some(FieldValue::value(value)),
1605    }
1606}
1607
1608/// An error carrying the code Hasura would give it.
1609///
1610/// The default coding reads the message, and a refusal written here has no
1611/// word in it that a message from PostgreSQL would not also have -- so it says
1612/// so itself rather than being guessed at.
1613fn coded_error(code: &'static str, message: impl Into<String>) -> async_graphql::Error {
1614    let mut error = async_graphql::Error::new(message);
1615    let mut extensions = async_graphql::ErrorExtensionValues::default();
1616    extensions.set("code", code);
1617    error.extensions = Some(extensions);
1618    error
1619}
1620
1621/// A refusal the client could have avoided by writing the request differently.
1622fn validation_error(message: impl Into<String>) -> async_graphql::Error {
1623    coded_error("validation-failed", message)
1624}
1625
1626/// A database error, classified the way Hasura classifies it.
1627///
1628/// PostgreSQL's own message is the whole of what either server says about
1629/// what went wrong; what Hasura adds is a word for *which kind* of wrong,
1630/// taken from the SQLSTATE rather than from the text: `Uniqueness violation.
1631/// duplicate key value violates unique constraint "author_name_key"`. A client
1632/// reporting that to a user is reporting text it already ships.
1633///
1634/// Read from the code, never from the message. The message is localised, the
1635/// code is not, and every attempt to tell these apart by reading English is
1636/// wrong the first time a server runs in another language.
1637fn database_error(error: sqlx::Error) -> async_graphql::Error {
1638    let sqlx::Error::Database(db) = &error else {
1639        return async_graphql::Error::new(error.to_string());
1640    };
1641    let described = match db.code().as_deref() {
1642        // Class 23, integrity constraint violation: the four PostgreSQL
1643        // raises and Hasura names.
1644        Some("23505") => "Uniqueness violation. ",
1645        Some("23502") => "Not-NULL violation. ",
1646        Some("23503") => "Foreign key violation. ",
1647        Some("23514") => "Check constraint violation. ",
1648        // Everything else is answered with PostgreSQL's own words and no
1649        // heading. Inventing one for a class Hasura says nothing about would
1650        // be a difference rather than a match.
1651        _ => "",
1652    };
1653    let error = async_graphql::Error::new(format!("{}{}", described, db.message()));
1654    // And the code, where the SQLSTATE says which one. Only the codes Hasura's
1655    // corpus pins are set: everything else is left for `code_for` to classify,
1656    // which is a guess from the message text and says so. Replacing that guess
1657    // wholesale would be the same guess with fewer places to notice it.
1658    let coded = match db.code().as_deref() {
1659        Some(code) if code.starts_with("23") => Some("constraint-violation"),
1660        // A `LIKE` pattern that ends mid-escape. Not a data exception to
1661        // Hasura -- the pattern came from the request, so the request is what
1662        // was wrong.
1663        Some("22025") => Some("bad-request"),
1664        // A negative `OFFSET`, and the rest of class 22 with it.
1665        Some(code) if code.starts_with("22") => Some("data-exception"),
1666        _ => None,
1667    };
1668    match coded {
1669        Some(code) => at_code(error, code),
1670        None => error,
1671    }
1672}
1673
1674/// The same error, coded as Hasura codes it.
1675fn at_code(mut error: async_graphql::Error, code: &'static str) -> async_graphql::Error {
1676    let mut extensions = error.extensions.take().unwrap_or_default();
1677    extensions.set("code", code);
1678    error.extensions = Some(extensions);
1679    error
1680}
1681
1682/// The same error, told where in the request it happened.
1683///
1684/// Hasura names the place a write went wrong inside the *arguments* --
1685/// `$.selectionSet.insert_author.args.objects[0].bio` -- and a response path
1686/// cannot say that: it names fields of the answer, and the answer has no
1687/// `objects`. So the path is written where the error is raised, by whoever
1688/// knows which row and which column it is about, and
1689/// [`crate::hasura::path_for`] prefers it over the response path.
1690fn at_path(mut error: async_graphql::Error, path: &str) -> async_graphql::Error {
1691    let mut extensions = error.extensions.take().unwrap_or_default();
1692    extensions.set("path", path);
1693    error.extensions = Some(extensions);
1694    error
1695}
1696
1697fn create_object_type(
1698    obj: &TableObjectType,
1699    relationships: &[RelationshipField],
1700    federation_entity: Option<&FederationEntity>,
1701) -> Object {
1702    let mut object = Object::new(&obj.name);
1703    let entity_is_shared = federation_entity.is_some_and(|entity| entity.shared);
1704    if let Some(entity) = federation_entity {
1705        object = object.key(entity.key_fields_sdl());
1706    }
1707
1708    // A table with no comment still gets a description, because Hasura gives
1709    // one and a client generating documentation from the schema would
1710    // otherwise show a blank where it used to show this.
1711    object = match obj.description() {
1712        // An empty description is one that was given and is empty: metadata
1713        // said the type has none, which is not the same as saying nothing.
1714        Some("") => object,
1715        Some(desc) => object.description(desc),
1716        None => object.description(format!(
1717            "columns and relationships of \"{}\"",
1718            obj.table_name()
1719        )),
1720    };
1721
1722    // A GraphQL object may not have two fields of one name, and a schema that
1723    // tries to build one aborts the process rather than returning an error.
1724    // Relationship names are derived from the table they point at, so a
1725    // foreign key column named after its own target -- `pizza.crust`
1726    // referencing `crust`, which is an ordinary way to write that schema --
1727    // produces exactly that clash.
1728    //
1729    // The column wins. It is the table's own data, it is what a client's
1730    // existing queries select, and dropping it to keep a derived name would
1731    // lose something the schema actually says. The relationship is left out
1732    // and said so, which is what Hasura does with the same clash: the
1733    // metadata is marked inconsistent and the field is simply not there.
1734    let mut taken: HashSet<String> = obj.fields.iter().map(|f| f.name.clone()).collect();
1735
1736    // Collected rather than added as they are built, so they can go on in name
1737    // order -- which is the order Hasura answers introspection in, and does not
1738    // depend on which column the catalogue happened to list first.
1739    let mut fields: Vec<(String, Field)> = Vec::new();
1740
1741    for field in &obj.fields {
1742        let field_name = field.name.clone();
1743        let field_type = graphql_type_ref(&field.type_string());
1744        // A document-valued column can be asked for one part of itself, the
1745        // same way `#>` reads one. It is answered here rather than in SQL
1746        // because the same column may be asked for under several aliases --
1747        // `c32_json(path: "a")` beside `c32_json(path: "arr[0]")` -- and
1748        // one projection cannot carry both.
1749        let takes_path = matches!(&field.graphql_type, crate::types::GraphQLType::Json)
1750            || matches!(
1751                &field.graphql_type,
1752                crate::types::GraphQLType::Custom(name) if name == "json"
1753            );
1754
1755        // Create field with resolver that extracts from parent async_graphql::Value
1756        // The query resolver stores rows as FieldValue::value(Value::Object)
1757        // so we use as_value() to get the Value and extract fields from the Object
1758        let gql_field = Field::new(&field.name, field_type, move |ctx| {
1759            let field_name = field_name.clone();
1760            FieldFuture::new(async move {
1761                // Get the parent value as async_graphql::Value using as_value()
1762                if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1763                    // Convert field name to async_graphql::Name for lookup
1764                    let key = async_graphql::Name::new(&field_name);
1765                    let path = match takes_path {
1766                        true => ctx.args.get("path").and_then(|v| v.string().ok()),
1767                        false => None,
1768                    };
1769                    match map.get(&key) {
1770                        // A null is the answer, not a value to resolve. An
1771                        // enum-typed column said so with `internal: invalid
1772                        // item for enum` rather than answering null, because a
1773                        // null is not one of its members.
1774                        Some(Value::Null) => return Ok(None),
1775                        Some(val) => {
1776                            let val = match path {
1777                                Some(path) => walk_json_path(val, path)?,
1778                                None => val.clone(),
1779                            };
1780                            return match val {
1781                                Value::Null => Ok(None),
1782                                val => Ok(Some(FieldValue::value(val))),
1783                            };
1784                        }
1785                        None => {}
1786                    }
1787                }
1788
1789                // Field not found or parent not a Value::Object
1790                Ok(None)
1791            })
1792        });
1793
1794        let gql_field = match takes_path {
1795            true => gql_field.argument(
1796                InputValue::new("path", TypeRef::named("String")).description("JSON select path"),
1797            ),
1798            false => gql_field,
1799        };
1800
1801        // A computed field whose function takes more than the row: the
1802        // caller writes those under `args`, for the reason a computed
1803        // relationship's are written there.
1804        let gql_field = match field.arguments.is_empty() {
1805            true => gql_field,
1806            false => gql_field.argument(InputValue::new(
1807                "args",
1808                TypeRef::named_nn(computed_args_type_name(&obj.local_name, &field.name)),
1809            )),
1810        };
1811
1812        let gql_field = if let Some(desc) = &field.description {
1813            gql_field.description(desc)
1814        } else {
1815            gql_field
1816        };
1817        let gql_field = if entity_is_shared && !field.is_pk {
1818            gql_field.shareable()
1819        } else {
1820            gql_field
1821        };
1822
1823        fields.push((field.name.clone(), gql_field));
1824    }
1825
1826    // Relationship fields. The query resolver embeds related rows into the
1827    // parent JSON before returning it, so these read from the parent value the
1828    // same way column fields do.
1829    for rel in relationships {
1830        if !taken.insert(rel.name.clone()) {
1831            tracing::warn!(
1832                "{}: not exposing the relationship to \"{}\" as `{}` -- the table \
1833                 already has a column of that name",
1834                obj.name,
1835                rel.target_type,
1836                rel.name
1837            );
1838            continue;
1839        }
1840        let field_name = rel.name.clone();
1841        let field_type = if rel.is_list {
1842            TypeRef::named_nn_list_nn(&rel.target_type)
1843        } else if always_present(rel, &obj.table) {
1844            TypeRef::named_nn(&rel.target_type)
1845        } else {
1846            TypeRef::named(&rel.target_type)
1847        };
1848
1849        let is_list = rel.is_list;
1850        let mut gql_field = Field::new(&rel.name, field_type, move |ctx| {
1851            let field_name = field_name.clone();
1852            FieldFuture::new(async move {
1853                if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1854                    let key = async_graphql::Name::new(&field_name);
1855                    match map.get(&key) {
1856                        // A row that points at nothing has no related row, and
1857                        // the answer to that is null -- not an object whose
1858                        // every field is null, which is what handing a null
1859                        // parent value to the field resolvers produced.
1860                        Some(Value::Null) if !is_list => return Ok(None),
1861                        Some(val) => return Ok(Some(FieldValue::value(val.clone()))),
1862                        None => {}
1863                    }
1864                }
1865                Ok(None)
1866            })
1867        });
1868
1869        // The same arguments the root field takes. An embedded list is a list
1870        // like any other: a client showing the five most recent articles of an
1871        // author has nowhere else to say so, and without these the only way to
1872        // narrow one is to fetch all of it and discard the rest in the client.
1873        // A to-one relationship is left alone -- there is nothing to order or
1874        // page through when the answer is one row.
1875        // A computed relationship's function may take more than the row --
1876        // "the articles of this author matching a search" -- and `args` is
1877        // where the caller writes them, so a term called `limit` cannot shadow
1878        // the one that pages the result.
1879        if !rel.arguments.is_empty() {
1880            gql_field = gql_field.argument(InputValue::new(
1881                "args",
1882                TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1883            ));
1884        }
1885        if rel.is_list {
1886            gql_field = gql_field
1887                .argument(InputValue::new(
1888                    "distinct_on",
1889                    TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1890                        &rel.target_local_name,
1891                    )),
1892                ))
1893                .argument(InputValue::new(
1894                    "where",
1895                    TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1896                        &rel.target_local_name,
1897                    )),
1898                ))
1899                .argument(InputValue::new(
1900                    "order_by",
1901                    TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1902                        &rel.target_local_name,
1903                    )),
1904                ))
1905                .argument(InputValue::new("limit", TypeRef::named("Int")))
1906                .argument(InputValue::new("offset", TypeRef::named("Int")));
1907        }
1908
1909        let gql_field = if let Some(desc) = &rel.description {
1910            gql_field.description(desc)
1911        } else {
1912            gql_field
1913        };
1914        let gql_field = if entity_is_shared {
1915            gql_field.shareable()
1916        } else {
1917            gql_field
1918        };
1919
1920        fields.push((rel.name.clone(), gql_field));
1921
1922        // `author { articles_aggregate { aggregate { count } } }` -- the count
1923        // of a row's children without fetching them, which is the query behind
1924        // every "12 comments" beside a post. Only for a relationship to many:
1925        // counting one row is not a question anyone asks.
1926        if rel.is_list {
1927            let aggregate_field = format!("{}_aggregate", rel.name);
1928            if taken.insert(aggregate_field.clone()) {
1929                let key = aggregate_field.clone();
1930                fields.push((
1931                    aggregate_field.clone(),
1932                    Field::new(
1933                        &aggregate_field,
1934                        TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
1935                            &rel.target_local_name,
1936                        )),
1937                        move |ctx| {
1938                            let key = key.clone();
1939                            FieldFuture::new(async move {
1940                                // Absent where the parent has no children at
1941                                // all; the aggregate type's own resolvers read
1942                                // a missing key as zero and an empty list.
1943                                Ok(Some(FieldValue::value(
1944                                    child_value(&ctx, &key).unwrap_or(Value::Null),
1945                                )))
1946                            })
1947                        },
1948                    )
1949                    // The same arguments the list itself takes. "How many of
1950                    // this author's articles were published this year" is the
1951                    // question a count is usually asked as, and without a
1952                    // `where` there is no way to write it.
1953                    .argument(InputValue::new(
1954                        "distinct_on",
1955                        TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1956                            &rel.target_local_name,
1957                        )),
1958                    ))
1959                    .argument(InputValue::new(
1960                        "where",
1961                        TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1962                            &rel.target_local_name,
1963                        )),
1964                    ))
1965                    .argument(InputValue::new(
1966                        "order_by",
1967                        TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1968                            &rel.target_local_name,
1969                        )),
1970                    ))
1971                    .argument(InputValue::new("limit", TypeRef::named("Int")))
1972                    .argument(InputValue::new("offset", TypeRef::named("Int")))
1973                    .description(format!("Aggregates over {}.", rel.name)),
1974                ));
1975                // Counting the rows a function answers with means calling it,
1976                // so the aggregate takes whatever the function takes.
1977                if !rel.arguments.is_empty() {
1978                    let last = fields.len() - 1;
1979                    let (name, field) = fields.remove(last);
1980                    fields.push((
1981                        name,
1982                        field.argument(InputValue::new(
1983                            "args",
1984                            TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1985                        )),
1986                    ));
1987                }
1988                if entity_is_shared {
1989                    let last = fields.len() - 1;
1990                    let (name, field) = fields.remove(last);
1991                    fields.push((name, field.shareable()));
1992                }
1993            }
1994        }
1995    }
1996
1997    fields.sort_by(|(a, _), (b, _)| a.cmp(b));
1998    for (_, field) in fields {
1999        object = object.field(field);
2000    }
2001
2002    object
2003}
2004
2005/// A root field, whichever root it is on.
2006///
2007/// A query field and a subscription field take the same arguments and mean the
2008/// same by them -- the difference is only whether the answer is sent once or
2009/// whenever it changes -- and the two builders share no trait of their own, so
2010/// this is what lets the argument list be written once.
2011trait RootField: Sized {
2012    fn with_argument(self, value: InputValue) -> Self;
2013}
2014
2015impl RootField for Field {
2016    fn with_argument(self, value: InputValue) -> Self {
2017        self.argument(value)
2018    }
2019}
2020
2021impl RootField for SubscriptionField {
2022    fn with_argument(self, value: InputValue) -> Self {
2023        self.argument(value)
2024    }
2025}
2026
2027/// The five arguments that narrow a set of rows, in the order Hasura lists
2028/// them.
2029fn with_row_arguments<F: RootField>(field: F, type_name: &str) -> F {
2030    with_row_arguments_named(field, type_name, true)
2031}
2032
2033/// The same, where `columns` says whether the table has any to name.
2034///
2035/// A table a role may count and not read has none: there is no column enum to
2036/// order by and none to be distinct on, so those two arguments are not there
2037/// -- and could not be, since the types they name are not built either. What
2038/// is left still narrows the set being counted, which is the whole of what
2039/// such a root is for.
2040fn with_row_arguments_named<F: RootField>(field: F, type_name: &str, columns: bool) -> F {
2041    let mut field = field;
2042    if columns {
2043        field = field.with_argument(InputValue::new(
2044            "distinct_on",
2045            TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(type_name)),
2046        ));
2047    }
2048    field = field
2049        .with_argument(InputValue::new("limit", TypeRef::named("Int")))
2050        .with_argument(InputValue::new("offset", TypeRef::named("Int")));
2051    if columns {
2052        field = field.with_argument(InputValue::new(
2053            "order_by",
2054            TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(type_name)),
2055        ));
2056    }
2057    field.with_argument(InputValue::new(
2058        "where",
2059        TypeRef::named(crate::input::bool_exp::bool_exp_type_name(type_name)),
2060    ))
2061}
2062
2063/// One argument per primary key column, named and typed after the column
2064/// itself rather than assuming an integer `id`.
2065fn with_key_arguments<F: RootField>(
2066    field: F,
2067    schema_name: &str,
2068    table_name: &str,
2069    pk_columns: &[(String, String)],
2070    names: &crate::names::NameOverrides,
2071) -> F {
2072    let mut field = field;
2073    for (column, pg_type) in pk_columns {
2074        field = field.with_argument(InputValue::new(
2075            names
2076                .column(schema_name, table_name, column)
2077                .unwrap_or(column),
2078            TypeRef::named_nn(exposed_pk_argument_type(
2079                names,
2080                schema_name,
2081                table_name,
2082                column,
2083                pg_type,
2084            )),
2085        ));
2086    }
2087    field
2088}
2089
2090/// Create the Query type with all table query fields.
2091fn create_query_type(
2092    generated: &GeneratedSchema,
2093    root_name: &str,
2094    max_rows: Option<i64>,
2095    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2096    names: Arc<crate::names::NameOverrides>,
2097) -> Object {
2098    // Collected rather than added as they are built, so they can go on in name
2099    // order: that is the order Hasura answers introspection in, and it is the
2100    // order a client diffing two schemas, or a generator writing its types
2101    // out, gets a stable answer from.
2102    let mut roots: Vec<(String, Field)> = Vec::new();
2103
2104    for field in &generated.query_fields {
2105        let table_name = field.table_name.clone();
2106        let schema_name = field.schema_name.clone();
2107        let type_name = field.type_name.clone();
2108        let local_type_name = field.local_type_name.clone();
2109        let is_by_pk = field.is_by_pk;
2110        let pk_columns = field.pk_columns.clone();
2111        let return_type = graphql_type_ref(&field.return_type);
2112
2113        let spec_type_name = local_type_name.clone();
2114        let spec = Arc::new(QueryFieldSpec {
2115            schema_name,
2116            table_name,
2117            type_name,
2118            is_by_pk,
2119            pk_columns: pk_columns.clone(),
2120            max_rows,
2121            relationships: Arc::clone(&relationships),
2122            names: Arc::clone(&names),
2123            call: None,
2124        });
2125
2126        let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2127            let spec = Arc::clone(&spec);
2128            FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
2129        });
2130
2131        // Add standard query arguments
2132        gql_field = match is_by_pk {
2133            false => with_row_arguments(gql_field, &spec_type_name),
2134            true => with_key_arguments(
2135                gql_field,
2136                &field.schema_name,
2137                &field.table_name,
2138                &pk_columns,
2139                names.as_ref(),
2140            ),
2141        };
2142
2143        if let Some(desc) = &field.description {
2144            gql_field = gql_field.description(desc);
2145        }
2146
2147        // A table this role may count and not read has no row type, so the
2148        // list root that would answer with one is not built. The entry is
2149        // still here for the aggregate root below, which is what such a role
2150        // was granted.
2151        if field.rows {
2152            roots.push((field.name.clone(), gql_field));
2153        }
2154
2155        // The same rows, with numbers about them. Same arguments as the list
2156        // field, because `author_aggregate(where: ...)` counts the set the
2157        // filter describes, not the whole table.
2158        //
2159        // Not built at all for a role whose permission withholds it: counting
2160        // rows is a way of learning about them, so it is granted separately
2161        // from reading them and refused by absence rather than at execution.
2162        if !is_by_pk && field.aggregates {
2163            let agg_spec = Arc::new(AggregateSpec {
2164                schema_name: field.schema_name.clone(),
2165                table_name: field.table_name.clone(),
2166                type_name: field.type_name.clone(),
2167                max_rows,
2168                relationships: Arc::clone(&relationships),
2169                names: Arc::clone(&names),
2170                call: None,
2171            });
2172            let aggregate_field_name = field.aggregate_name.clone().unwrap_or_else(|| {
2173                crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2174            });
2175            let aggregate_field_name_for_sorting = aggregate_field_name.clone();
2176            let mut agg_field = Field::new(
2177                aggregate_field_name,
2178                TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2179                    &field.local_type_name,
2180                )),
2181                move |ctx| {
2182                    let agg_spec = Arc::clone(&agg_spec);
2183                    FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
2184                },
2185            );
2186            agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2187            agg_field = match field.aggregate_description.as_deref() {
2188                Some("") => agg_field,
2189                Some(given) => agg_field.description(given),
2190                None => agg_field.description(format!(
2191                    "fetch aggregated fields from the table: \"{}\"",
2192                    field.table_name
2193                )),
2194            };
2195            roots.push((aggregate_field_name_for_sorting, agg_field));
2196        }
2197    }
2198
2199    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2200    let mut query = Object::new(root_name);
2201    // A GraphQL object may not have no fields, so a schema that exposes no
2202    // table needs something on its query root. Hasura puts a placeholder there
2203    // and calls it this; a client that reaches it has nothing to read.
2204    if roots.is_empty() {
2205        return query.field(
2206            Field::new("no_queries_available", TypeRef::named("String"), |_| {
2207                FieldFuture::new(async move { Ok(None::<Value>) })
2208            })
2209            .description("There are no queries available to the current role."),
2210        );
2211    }
2212    for (_, field) in roots {
2213        query = query.field(field);
2214    }
2215
2216    query
2217}
2218
2219/// One document operator: its name, and the type its columns are given.
2220type JsonbOperator = (&'static str, fn() -> TypeRef);
2221
2222/// What a document column may be told to do, and what each is told with.
2223///
2224/// `_append` and `_prepend` take another document; `_delete_key` takes a key;
2225/// `_delete_elem` takes an index into an array; `_delete_at_path` takes the
2226/// path to what is being removed. The shapes are per operator rather than per
2227/// column, which is why one input type per operator says all of it.
2228const JSONB_OPERATORS: &[JsonbOperator] = &[
2229    ("_append", || TypeRef::named("jsonb")),
2230    ("_delete_at_path", || TypeRef::named_nn_list("String")),
2231    ("_delete_elem", || TypeRef::named("Int")),
2232    ("_delete_key", || TypeRef::named("String")),
2233    ("_prepend", || TypeRef::named("jsonb")),
2234];
2235
2236/// The name of the input holding one document operator's columns.
2237fn jsonb_operator_input(type_name: &str, operator: &str) -> String {
2238    format!("{}{}_input", type_name, operator)
2239}
2240
2241/// Add the operators an update may be written with.
2242///
2243/// All optional, and at least one required -- which GraphQL cannot express, so
2244/// the resolver says so instead of the schema. Making `_set` non-null would
2245/// have been expressible and wrong: an update that only increments a counter
2246/// never sends one.
2247fn with_update_operators(
2248    field: Field,
2249    base_name: &str,
2250    has_numeric: bool,
2251    has_jsonb: bool,
2252) -> Field {
2253    let mut field = field;
2254    // A table with no document column is not offered the document operators:
2255    // there is nothing there to append to.
2256    if has_jsonb {
2257        for (operator, _) in JSONB_OPERATORS {
2258            field = field.argument(InputValue::new(
2259                *operator,
2260                TypeRef::named(jsonb_operator_input(base_name, operator)),
2261            ));
2262        }
2263    }
2264    if has_numeric {
2265        field = field.argument(InputValue::new(
2266            "_inc",
2267            TypeRef::named(format!("{}_inc_input", base_name)),
2268        ));
2269    }
2270    field.argument(InputValue::new(
2271        "_set",
2272        TypeRef::named(format!("{}_set_input", base_name)),
2273    ))
2274}
2275
2276/// Create the Mutation type with all mutation fields.
2277/// Which columns a write may name, and so which of its inputs exist.
2278///
2279/// One definition, called both where the inputs are registered and where the
2280/// arguments naming them are declared. They used to agree by coincidence --
2281/// both read the object type's fields -- and stopped the moment a write input
2282/// began following its own permission. An argument naming a type nobody
2283/// registered is a schema async-graphql refuses to build, which costs a role
2284/// every field it has over the one it cannot use.
2285fn write_fields<'a>(
2286    object: &'a crate::schema::object::TableObjectType,
2287    names: &crate::names::NameOverrides,
2288    role: Option<&str>,
2289    verb: crate::role::Verb,
2290) -> Vec<&'a crate::schema::object::GraphQLField> {
2291    let allowed = role.and_then(|role| {
2292        names
2293            .permissions(&object.table.schema, &object.table.name, role)
2294            .and_then(|granted| granted.write_columns(verb))
2295    });
2296    object
2297        .writable_fields
2298        .iter()
2299        .filter(|field| {
2300            let column = table_column_for(names, &object.table, &field.name);
2301            // A column PostgreSQL generates always is not one a write may
2302            // name. Leaving it in the input type moved the refusal to the
2303            // database, which answers `cannot insert a non-DEFAULT value into
2304            // column "id"` and has by then forgotten which argument the value
2305            // came from. Out of the type, the walk over the request says
2306            // `field 'id' not found in type: 'author_insert_input'` and can
2307            // still point at it.
2308            object
2309                .table
2310                .get_column(column)
2311                .is_some_and(|c| !c.always_generated)
2312                && allowed.is_none_or(|set| set.allows(column))
2313        })
2314        .collect()
2315}
2316
2317/// Whether a type has an `_inc_input` and a set of document operator inputs.
2318fn update_inputs(
2319    object: &crate::schema::object::TableObjectType,
2320    names: &crate::names::NameOverrides,
2321    role: Option<&str>,
2322) -> (bool, bool) {
2323    let fields = write_fields(object, names, role, crate::role::Verb::Update);
2324    (
2325        fields
2326            .iter()
2327            .any(|f| crate::schema::aggregate::is_incrementable(&f.graphql_type)),
2328        fields
2329            .iter()
2330            .any(|f| matches!(&f.graphql_type, crate::types::GraphQLType::Json)),
2331    )
2332}
2333
2334fn create_mutation_type(
2335    generated: &GeneratedSchema,
2336    root_name: &str,
2337    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2338    type_names: Arc<HashMap<(String, String), String>>,
2339    names: Arc<crate::names::NameOverrides>,
2340    max_rows: Option<i64>,
2341    role: Option<&str>,
2342) -> Object {
2343    let mut mutation = Object::new(root_name);
2344    // In name order, for the reason the query root is: see there.
2345    let mut roots: Vec<(String, Field)> = Vec::new();
2346
2347    // Only a table with a unique constraint has a conflict to name, and only
2348    // those got the types for it.
2349    // Which tables have something to add to, and which have a document to
2350    // operate on. Read from what was registered above rather than worked out
2351    // again from the object types: the second answer is the one that can
2352    // drift, and drifting means an argument naming a type that is not there.
2353    let mut has_numeric_column: HashSet<String> = HashSet::new();
2354    let mut has_jsonb_column: HashSet<String> = HashSet::new();
2355    for object in generated.object_types.values() {
2356        let (numeric, jsonb) = update_inputs(object, &names, role);
2357        if numeric {
2358            has_numeric_column.insert(object.local_name.clone());
2359        }
2360        if jsonb {
2361            has_jsonb_column.insert(object.local_name.clone());
2362        }
2363    }
2364
2365    let has_conflict_target: HashSet<String> = generated
2366        .object_types
2367        .iter()
2368        .filter(|(_, object)| !object.table.unique_constraints.is_empty())
2369        .map(|(_, object)| object.local_name.clone())
2370        .collect();
2371
2372    for field in &generated.mutation_fields {
2373        let table_name = field.table_name.clone();
2374        let schema_name = field.schema_name.clone();
2375        let mutation_type = field.mutation_type;
2376        let pk_columns = field.pk_columns.clone();
2377        let return_type = graphql_type_ref(&field.return_type);
2378
2379        // The table's own type name, which is what its boolean expression is
2380        // named after. A bulk mutation returns `[author!]!` and a by-key one
2381        // returns `author`; both name the same table.
2382        let where_type = field.local_type_name.clone();
2383
2384        let resolver_pk_columns = pk_columns.clone();
2385        let field_relationships = Arc::clone(&relationships);
2386        let field_type_names = Arc::clone(&type_names);
2387        let field_names = Arc::clone(&names);
2388        let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2389            let table_name = table_name.clone();
2390            let schema_name = schema_name.clone();
2391            let pk_columns = resolver_pk_columns.clone();
2392            let relationships = Arc::clone(&field_relationships);
2393            let type_names = Arc::clone(&field_type_names);
2394            let names = Arc::clone(&field_names);
2395            FieldFuture::new(async move {
2396                resolve_mutation(
2397                    &ctx,
2398                    &schema_name,
2399                    &table_name,
2400                    mutation_type,
2401                    &pk_columns,
2402                    &relationships,
2403                    &type_names,
2404                    &names,
2405                    max_rows,
2406                )
2407                .await
2408            })
2409        });
2410
2411        // Add mutation-specific arguments.
2412        //
2413        // A by-PK mutation takes the key columns rather than a `where` object:
2414        // it is meant to address exactly one row, and accepting `where` made it
2415        // an ordinary bulk mutation that happened to return the first result.
2416        match mutation_type {
2417            MutationType::Insert | MutationType::InsertOne => {
2418                let insert_input = format!("{}_insert_input", where_type);
2419                gql_field = if mutation_type == MutationType::Insert {
2420                    gql_field.argument(InputValue::new(
2421                        "objects",
2422                        TypeRef::named_nn_list_nn(&insert_input),
2423                    ))
2424                } else {
2425                    // A single insert takes `object`, not a one-element
2426                    // `objects`.
2427                    gql_field.argument(InputValue::new("object", TypeRef::named_nn(&insert_input)))
2428                };
2429                if has_conflict_target.contains(&where_type) {
2430                    gql_field = gql_field.argument(InputValue::new(
2431                        "on_conflict",
2432                        TypeRef::named(format!("{}_on_conflict", where_type)),
2433                    ));
2434                }
2435            }
2436            MutationType::UpdateByPk => {
2437                gql_field = gql_field.argument(InputValue::new(
2438                    "pk_columns",
2439                    TypeRef::named_nn(format!("{}_pk_columns_input", where_type)),
2440                ));
2441                gql_field = with_update_operators(
2442                    gql_field,
2443                    &where_type,
2444                    has_numeric_column.contains(&where_type),
2445                    has_jsonb_column.contains(&where_type),
2446                );
2447            }
2448            MutationType::Update => {
2449                gql_field = gql_field.argument(InputValue::new(
2450                    "where",
2451                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2452                ));
2453                gql_field = with_update_operators(
2454                    gql_field,
2455                    &where_type,
2456                    has_numeric_column.contains(&where_type),
2457                    has_jsonb_column.contains(&where_type),
2458                );
2459            }
2460            MutationType::UpdateMany => {
2461                gql_field = gql_field.argument(InputValue::new(
2462                    "updates",
2463                    TypeRef::named_nn_list_nn(format!("{}_updates", where_type)),
2464                ));
2465            }
2466            MutationType::DeleteByPk => {
2467                for (col_name, pg_type) in &pk_columns {
2468                    gql_field = gql_field.argument(InputValue::new(
2469                        names
2470                            .column(&field.schema_name, &field.table_name, col_name)
2471                            .unwrap_or(col_name),
2472                        TypeRef::named_nn(exposed_pk_argument_type(
2473                            names.as_ref(),
2474                            &field.schema_name,
2475                            &field.table_name,
2476                            col_name,
2477                            pg_type,
2478                        )),
2479                    ));
2480                }
2481            }
2482            MutationType::Delete => {
2483                // Required, as it is on an update: a delete with no predicate
2484                // is a delete of the whole table, and this refused one at
2485                // execution while the schema said it was a query worth
2486                // writing. Saying so in the type is what Hasura does, and it
2487                // is what a client's own tooling can catch before the request
2488                // is sent.
2489                gql_field = gql_field.argument(InputValue::new(
2490                    "where",
2491                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2492                ));
2493            }
2494        }
2495
2496        if let Some(desc) = &field.description {
2497            gql_field = gql_field.description(desc);
2498        }
2499
2500        roots.push((field.name.clone(), gql_field));
2501    }
2502
2503    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2504    for (_, field) in roots {
2505        mutation = mutation.field(field);
2506    }
2507
2508    mutation
2509}
2510
2511/// Create the Subscription type: the query root, answered again on change.
2512///
2513/// A subscription here is a **live query**. It carries the same fields the
2514/// query root does, takes the same arguments, and answers with the same rows
2515/// -- the difference is that the answer is sent again whenever it stops being
2516/// true. That is the contract a client generated against Hasura expects, and
2517/// the reason its `subscription_root` is a mirror of its `query_root`.
2518///
2519/// What wakes it is this server's own: a trigger publishes on the changed
2520/// table and the query is read again, which costs nothing while nothing is
2521/// written and answers in milliseconds when something is. A trigger cannot
2522/// see everything a query can -- a view has none, an embedded row may live in
2523/// a table that carries none, and `where: {expires_at: {_lt: "now()"}}`
2524/// changes with no write at all -- so a slow refresh runs beside it and
2525/// closes exactly those gaps. Polling alone is what Hasura does, and it pays
2526/// for every subscriber every tick whether or not anything happened.
2527fn create_subscription_type(
2528    generated: &GeneratedSchema,
2529    root_name: &str,
2530    max_rows: Option<i64>,
2531    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2532    names: Arc<crate::names::NameOverrides>,
2533    refresh: std::time::Duration,
2534) -> Subscription {
2535    let mut roots: Vec<(String, SubscriptionField)> = Vec::new();
2536
2537    for field in &generated.query_fields {
2538        let spec_type_name = field.local_type_name.clone();
2539        let pk_columns = field.pk_columns.clone();
2540        let is_by_pk = field.is_by_pk;
2541        let return_type = graphql_type_ref(&field.return_type);
2542        let watched = vec![crate::subscription::table_channel_name(
2543            &field.schema_name,
2544            &field.table_name,
2545        )];
2546
2547        let spec = Arc::new(QueryFieldSpec {
2548            schema_name: field.schema_name.clone(),
2549            table_name: field.table_name.clone(),
2550            type_name: field.type_name.clone(),
2551            is_by_pk,
2552            pk_columns: pk_columns.clone(),
2553            max_rows,
2554            relationships: Arc::clone(&relationships),
2555            names: Arc::clone(&names),
2556            call: None,
2557        });
2558
2559        let mut gql_field = SubscriptionField::new(&field.name, return_type, move |ctx| {
2560            let spec = Arc::clone(&spec);
2561            let watched = watched.clone();
2562            SubscriptionFieldFuture::new(async move {
2563                let wake = wake_on(&ctx, &watched, refresh).await;
2564                Ok(live(ctx, LiveSource::Rows(spec), wake))
2565            })
2566        });
2567
2568        gql_field = match is_by_pk {
2569            false => with_row_arguments(gql_field, &spec_type_name),
2570            true => with_key_arguments(
2571                gql_field,
2572                &field.schema_name,
2573                &field.table_name,
2574                &pk_columns,
2575                names.as_ref(),
2576            ),
2577        };
2578        if let Some(desc) = &field.description {
2579            gql_field = gql_field.description(desc);
2580        }
2581        // No row type, no root that answers with one -- see the query root.
2582        if field.rows {
2583            roots.push((field.name.clone(), gql_field));
2584        }
2585
2586        // A live query mirrors the query root, so an aggregate the role may not
2587        // ask for once is not one it may ask for continuously either.
2588        if is_by_pk || !field.aggregates {
2589            continue;
2590        }
2591
2592        // The same rows, with numbers about them, watched the same way.
2593        let agg_spec = Arc::new(AggregateSpec {
2594            schema_name: field.schema_name.clone(),
2595            table_name: field.table_name.clone(),
2596            type_name: field.type_name.clone(),
2597            max_rows,
2598            relationships: Arc::clone(&relationships),
2599            names: Arc::clone(&names),
2600            call: None,
2601        });
2602        let agg_watched = vec![crate::subscription::table_channel_name(
2603            &field.schema_name,
2604            &field.table_name,
2605        )];
2606        let agg_name = field.aggregate_name.clone().unwrap_or_else(|| {
2607            crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2608        });
2609        let mut agg_field = SubscriptionField::new(
2610            agg_name.clone(),
2611            TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2612                &field.local_type_name,
2613            )),
2614            move |ctx| {
2615                let agg_spec = Arc::clone(&agg_spec);
2616                let watched = agg_watched.clone();
2617                SubscriptionFieldFuture::new(async move {
2618                    let wake = wake_on(&ctx, &watched, refresh).await;
2619                    Ok(live(ctx, LiveSource::Aggregate(agg_spec), wake))
2620                })
2621            },
2622        );
2623        agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2624        agg_field = match field.aggregate_description.as_deref() {
2625            Some("") => agg_field,
2626            Some(given) => agg_field.description(given),
2627            None => agg_field.description(format!(
2628                "fetch aggregated fields from the table: \"{}\"",
2629                field.table_name
2630            )),
2631        };
2632        roots.push((agg_name, agg_field));
2633    }
2634
2635    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2636    let mut subscription = Subscription::new(root_name);
2637    // A GraphQL object may not have no fields, and a schema that exposes no
2638    // table still has a subscription root if subscriptions are on.
2639    if roots.is_empty() {
2640        return subscription.field(SubscriptionField::new(
2641            "no_subscriptions_available",
2642            TypeRef::named("String"),
2643            |_| {
2644                SubscriptionFieldFuture::new(async move {
2645                    Ok(futures::stream::empty::<
2646                        Result<FieldValue, async_graphql::Error>,
2647                    >())
2648                })
2649            },
2650        ));
2651    }
2652    for (_, field) in roots {
2653        subscription = subscription.field(field);
2654    }
2655    subscription
2656}
2657
2658/// What tells a live query its answer may have changed.
2659///
2660/// Every notification on the tables it reads, and a slow tick beside them.
2661/// Yields once per wake and never ends -- a subscription lasts as long as the
2662/// client holds it open.
2663async fn wake_on(
2664    ctx: &ResolverContext<'_>,
2665    channels: &[String],
2666    refresh: std::time::Duration,
2667) -> futures::stream::BoxStream<'static, ()> {
2668    use futures::StreamExt;
2669
2670    let mut streams: Vec<futures::stream::BoxStream<'static, ()>> =
2671        Vec::with_capacity(channels.len() + 1);
2672    // A tick even where nothing is listening: a table with no trigger, a view,
2673    // and a predicate that changes with the clock all depend on it.
2674    streams.push(
2675        tokio_stream::wrappers::IntervalStream::new({
2676            let mut interval = tokio::time::interval(refresh);
2677            interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
2678            interval
2679        })
2680        .map(|_| ())
2681        .boxed(),
2682    );
2683
2684    if let Ok(broker_arc) = ctx.data::<Arc<RwLock<Option<NotifyBroker>>>>() {
2685        let guard = broker_arc.read().await;
2686        if let Some(broker) = guard.as_ref() {
2687            for channel in channels {
2688                // `subscribe_or_create` rather than `subscribe`: a table whose
2689                // trigger was never installed has no channel, and a live query
2690                // on it is answered by the refresh rather than refused.
2691                streams.push(
2692                    broker
2693                        .subscribe_or_create(channel)
2694                        .await
2695                        .map(|_| ())
2696                        .boxed(),
2697                );
2698            }
2699        }
2700    }
2701
2702    futures::stream::select_all(streams).boxed()
2703}
2704
2705/// What a live query reads each time it is woken.
2706///
2707/// An enum rather than a closure because the answer borrows the context it is
2708/// read from, and a closure returning a future that borrows its argument is
2709/// not something a bound can say.
2710enum LiveSource {
2711    Rows(Arc<QueryFieldSpec>),
2712    Aggregate(Arc<AggregateSpec>),
2713}
2714
2715/// Read one answer, and the value to compare it against the last one by.
2716async fn read_live<'a>(
2717    ctx: &ResolverContext<'a>,
2718    source: &LiveSource,
2719) -> Result<(serde_json::Value, Option<FieldValue<'a>>), async_graphql::Error> {
2720    match source {
2721        LiveSource::Rows(spec) => {
2722            let rows = query_rows(ctx, spec).await?;
2723            Ok((
2724                serde_json::Value::Array(rows.clone()),
2725                rows_as_field_value(rows, spec.is_by_pk),
2726            ))
2727        }
2728        LiveSource::Aggregate(spec) => {
2729            let value = aggregate_value(ctx, spec).await?;
2730            let seen = serde_json::to_value(&value).unwrap_or_default();
2731            Ok((seen, Some(FieldValue::value(value))))
2732        }
2733    }
2734}
2735
2736/// A live query: the answer now, and the answer again each time it changes.
2737///
2738/// The first item is sent as soon as the client subscribes, which is what
2739/// makes this a query rather than a feed -- there is no window in which the
2740/// client knows nothing. After that, `answer` is read again on every wake and
2741/// sent only when what came back is not what was sent last: a write that
2742/// changes a row the subscription does not select is a wake and not a message.
2743fn live<'a>(
2744    ctx: ResolverContext<'a>,
2745    source: LiveSource,
2746    wake: futures::stream::BoxStream<'static, ()>,
2747) -> impl futures::Stream<Item = Result<FieldValue<'a>, async_graphql::Error>> + Send + 'a {
2748    use futures::StreamExt;
2749
2750    struct Live<'a> {
2751        ctx: ResolverContext<'a>,
2752        source: LiveSource,
2753        wake: futures::stream::BoxStream<'static, ()>,
2754        sent: Option<serde_json::Value>,
2755        started: bool,
2756        /// An error has been sent, and the subscription ends after it.
2757        failed: bool,
2758    }
2759
2760    futures::stream::unfold(
2761        Live {
2762            ctx,
2763            source,
2764            wake,
2765            sent: None,
2766            started: false,
2767            failed: false,
2768        },
2769        |mut live: Live<'a>| async move {
2770            if live.failed {
2771                return None;
2772            }
2773            loop {
2774                if live.started {
2775                    live.wake.next().await?;
2776                } else {
2777                    live.started = true;
2778                }
2779                let read = read_live(&live.ctx, &live.source).await;
2780                match read {
2781                    // An error ends the subscription: the client asked
2782                    // something that cannot be answered, and answering it
2783                    // again on the next tick would only repeat that. It is
2784                    // sent first, though -- ending on it unsent, as a
2785                    // `take_while(is_ok)` here once did, answers a refused
2786                    // subscription with a bare `complete` that reads exactly
2787                    // like one that finished.
2788                    Err(e) => {
2789                        live.failed = true;
2790                        return Some((Err(e), live));
2791                    }
2792                    Ok((seen, value)) => {
2793                        if live.sent.as_ref() == Some(&seen) {
2794                            continue;
2795                        }
2796                        live.sent = Some(seen);
2797                        let value = value.unwrap_or_else(|| FieldValue::value(Value::Null));
2798                        return Some((Ok(value), live));
2799                    }
2800                }
2801            }
2802        },
2803    )
2804}
2805
2806/// Everything a query field's resolver needs about the field it serves.
2807struct QueryFieldSpec {
2808    schema_name: String,
2809    table_name: String,
2810    type_name: String,
2811    is_by_pk: bool,
2812    pk_columns: Vec<(String, String)>,
2813    max_rows: Option<i64>,
2814    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2815    names: Arc<crate::names::NameOverrides>,
2816    /// Set where the rows come from a function rather than from the table
2817    /// itself. Everything else about reading them is the same.
2818    call: Option<Arc<FunctionCall>>,
2819}
2820
2821/// Everything `_entities` needs to resolve a representation to a table row.
2822#[derive(Clone)]
2823struct EntityLookup {
2824    schema_name: String,
2825    table_name: String,
2826    type_name: String,
2827    key_columns: Vec<EntityKeyColumn>,
2828    row_column_types: HashMap<String, String>,
2829}
2830
2831#[derive(Clone)]
2832struct EntityKeyColumn {
2833    field_name: String,
2834    column_name: String,
2835    pg_type: String,
2836    explicit_id: bool,
2837}
2838
2839/// The federation entity resolver's immutable schema view.
2840struct EntityResolverState {
2841    entities: HashMap<String, EntityLookup>,
2842    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2843    names: Arc<crate::names::NameOverrides>,
2844    max_rows: Option<i64>,
2845}
2846
2847impl EntityResolverState {
2848    fn new(
2849        generated: &GeneratedSchema,
2850        relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2851        names: Arc<crate::names::NameOverrides>,
2852        max_rows: Option<i64>,
2853    ) -> Self {
2854        let mut entities = HashMap::new();
2855        for (type_name, entity) in &generated.federation_entities {
2856            let Some(object) = generated.object_types.get(type_name) else {
2857                continue;
2858            };
2859            let mut key_columns = Vec::with_capacity(entity.key_fields.len());
2860            for field_name in &entity.key_fields {
2861                let column_name = names
2862                    .column_source(&object.table.schema, &object.table.name, field_name)
2863                    .unwrap_or(field_name);
2864                let Some(column) = object.table.get_column(column_name) else {
2865                    continue;
2866                };
2867                key_columns.push(EntityKeyColumn {
2868                    field_name: field_name.clone(),
2869                    column_name: column.name.clone(),
2870                    pg_type: column.nominal_type.clone(),
2871                    explicit_id: matches!(
2872                        names.column_type(&object.table.schema, &object.table.name, &column.name),
2873                        Some(crate::types::GraphQLType::Id)
2874                    ),
2875                });
2876            }
2877            if key_columns.len() != entity.key_fields.len() {
2878                continue;
2879            }
2880            entities.insert(
2881                type_name.clone(),
2882                EntityLookup {
2883                    schema_name: object.table.schema.clone(),
2884                    table_name: object.table.name.clone(),
2885                    type_name: type_name.clone(),
2886                    key_columns,
2887                    row_column_types: exposed_column_types(&object.table, names.as_ref()),
2888                },
2889            );
2890        }
2891        Self {
2892            entities,
2893            relationships,
2894            names,
2895            max_rows,
2896        }
2897    }
2898}
2899
2900struct EntityRepresentation {
2901    index: usize,
2902    type_name: String,
2903    values: serde_json::Map<String, serde_json::Value>,
2904}
2905
2906/// Fields selected for one concrete member of the federation `_Entity` union.
2907///
2908/// `SelectionField::selection_set()` expands every fragment but does not apply
2909/// its type condition. Keep a parallel applicability list while walking the
2910/// raw selection tree, then use it to retain only the fields meant for this
2911/// entity type.
2912fn entity_field_applicability(
2913    selection_set: &SelectionSet,
2914    fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2915    type_name: &str,
2916) -> Vec<bool> {
2917    fn condition_matches(condition: Option<&Positioned<TypeCondition>>, type_name: &str) -> bool {
2918        condition.is_none_or(|condition| {
2919            let condition = condition.node.on.node.as_str();
2920            condition == type_name || condition == "_Entity"
2921        })
2922    }
2923
2924    fn collect(
2925        selection_set: &SelectionSet,
2926        fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2927        type_name: &str,
2928        parent_matches: bool,
2929        applicability: &mut Vec<bool>,
2930    ) {
2931        for selection in &selection_set.items {
2932            match &selection.node {
2933                Selection::Field(_) => applicability.push(parent_matches),
2934                Selection::FragmentSpread(spread) => {
2935                    let Some(fragment) = fragments.get(&spread.node.fragment_name.node) else {
2936                        continue;
2937                    };
2938                    collect(
2939                        &fragment.node.selection_set.node,
2940                        fragments,
2941                        type_name,
2942                        parent_matches
2943                            && condition_matches(Some(&fragment.node.type_condition), type_name),
2944                        applicability,
2945                    );
2946                }
2947                Selection::InlineFragment(fragment) => collect(
2948                    &fragment.node.selection_set.node,
2949                    fragments,
2950                    type_name,
2951                    parent_matches
2952                        && condition_matches(fragment.node.type_condition.as_ref(), type_name),
2953                    applicability,
2954                ),
2955            }
2956        }
2957    }
2958
2959    let mut applicability = Vec::new();
2960    collect(
2961        selection_set,
2962        fragments,
2963        type_name,
2964        true,
2965        &mut applicability,
2966    );
2967    applicability
2968}
2969
2970fn entity_selection_fields<'ctx, 'a>(
2971    ctx: &'ctx ResolverContext<'a>,
2972    type_name: &str,
2973) -> Vec<SelectionField<'ctx>> {
2974    let applicability = entity_field_applicability(
2975        &ctx.ctx.item.node.selection_set.node,
2976        &ctx.ctx.query_env.fragments,
2977        type_name,
2978    );
2979    let fields: Vec<_> = ctx.field().selection_set().collect();
2980    debug_assert_eq!(fields.len(), applicability.len());
2981    fields
2982        .into_iter()
2983        .zip(applicability)
2984        .filter_map(|(field, applies)| applies.then_some(field))
2985        .collect()
2986}
2987
2988async fn resolve_entities<'a>(
2989    ctx: &ResolverContext<'a>,
2990    state: &EntityResolverState,
2991) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
2992    let representations = ctx.args.try_get("representations")?.list()?;
2993    let mut grouped: HashMap<String, Vec<EntityRepresentation>> = HashMap::new();
2994    let mut count = 0;
2995    for (index, representation) in representations.iter().enumerate() {
2996        count += 1;
2997        let value = accessor_to_json(&representation);
2998        let serde_json::Value::Object(values) = value else {
2999            return Err(async_graphql::Error::new(
3000                "entity representation must be an object",
3001            ));
3002        };
3003        let type_name = values
3004            .get("__typename")
3005            .and_then(serde_json::Value::as_str)
3006            .ok_or_else(|| {
3007                async_graphql::Error::new("entity representation is missing __typename")
3008            })?
3009            .to_string();
3010        grouped
3011            .entry(type_name.clone())
3012            .or_default()
3013            .push(EntityRepresentation {
3014                index,
3015                type_name,
3016                values,
3017            });
3018    }
3019
3020    let mut resolved: Vec<Option<FieldValue<'a>>> =
3021        std::iter::repeat_with(|| None).take(count).collect();
3022    for (type_name, representations) in grouped {
3023        let lookup = state.entities.get(&type_name).ok_or_else(|| {
3024            async_graphql::Error::new(format!("unknown federation entity \"{}\"", type_name))
3025        })?;
3026        let rows = resolve_entity_group(ctx, lookup, &representations, state).await?;
3027        for (ordinality, representation) in representations.into_iter().enumerate() {
3028            resolved[representation.index] = Some(match &rows[ordinality] {
3029                Some(row) => FieldValue::value(json_to_value(row.clone()))
3030                    .with_type(representation.type_name),
3031                None => FieldValue::NULL,
3032            });
3033        }
3034    }
3035
3036    Ok(Some(FieldValue::list(resolved.into_iter().map(|value| {
3037        value.expect("every representation was resolved or errored")
3038    }))))
3039}
3040
3041async fn resolve_entity_group(
3042    ctx: &ResolverContext<'_>,
3043    lookup: &EntityLookup,
3044    representations: &[EntityRepresentation],
3045    state: &EntityResolverState,
3046) -> Result<Vec<Option<serde_json::Value>>, async_graphql::Error> {
3047    if representations.is_empty() {
3048        return Ok(Vec::new());
3049    }
3050
3051    let pool = ctx.data::<PgPool>()?;
3052    let gql_ctx = ctx.data::<GraphQLContext>()?;
3053    let mut bound_values = Vec::new();
3054    let mut value_rows = Vec::with_capacity(representations.len());
3055    for (ordinality, representation) in representations.iter().enumerate() {
3056        let normalized_values = normalized_entity_key_values(
3057            &representation.values,
3058            &lookup.key_columns,
3059            &lookup.type_name,
3060        )?;
3061        let mut values = Vec::with_capacity(lookup.key_columns.len() + 1);
3062        for (key_column, value) in lookup.key_columns.iter().zip(normalized_values) {
3063            values.push(format!(
3064                "${}::{}",
3065                bound_values.len() + 1,
3066                key_column.pg_type
3067            ));
3068            bound_values.push(value);
3069        }
3070        values.push(ordinality.to_string());
3071        value_rows.push(format!("({})", values.join(", ")));
3072    }
3073
3074    let representation_alias = "pgrst_entity_representation";
3075    let ordinality_column = "pgrst_entity_ordinality";
3076    let key_aliases: Vec<String> = (0..lookup.key_columns.len())
3077        .map(|index| format!("pgrst_entity_key_{index}"))
3078        .collect();
3079    let mut representation_columns: Vec<String> = key_aliases
3080        .iter()
3081        .map(|column| postrust_sql::escape_ident(column))
3082        .collect();
3083    representation_columns.push(postrust_sql::escape_ident(ordinality_column));
3084    let representations_sql = format!(
3085        "(VALUES {}) AS {} ({})",
3086        value_rows.join(", "),
3087        postrust_sql::escape_ident(representation_alias),
3088        representation_columns.join(", ")
3089    );
3090    let key_predicate = lookup
3091        .key_columns
3092        .iter()
3093        .zip(&key_aliases)
3094        .map(|(key_column, key_alias)| {
3095            format!(
3096                "{}.{} = {}.{}",
3097                postrust_sql::escape_ident(READ_ROW),
3098                postrust_sql::escape_ident(&key_column.column_name),
3099                postrust_sql::escape_ident(representation_alias),
3100                postrust_sql::escape_ident(key_alias)
3101            )
3102        })
3103        .collect::<Vec<_>>()
3104        .join(" AND ");
3105    let mut where_sql = format!(" WHERE ({key_predicate})");
3106
3107    let permission = permission_predicate(
3108        &gql_ctx.caller(),
3109        state.names.as_ref(),
3110        &lookup.schema_name,
3111        &lookup.table_name,
3112    )?;
3113    if let Some(predicate) = permission {
3114        let guard = gql_ctx
3115            .schema_cache
3116            .get()
3117            .await
3118            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3119        let cache = guard
3120            .as_ref()
3121            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3122        let scope = WhereScope::table(
3123            &lookup.schema_name,
3124            &lookup.table_name,
3125            &lookup.type_name,
3126            state.names.as_ref(),
3127        )
3128        .under_alias(READ_ROW)
3129        .with_resolution(cache, state.relationships.as_ref())
3130        .for_caller(gql_ctx.caller());
3131        let (filter_sql, filter_values) =
3132            build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3133        if !filter_sql.is_empty() {
3134            where_sql = format!(
3135                "{} AND ({})",
3136                where_sql,
3137                filter_sql.trim_start_matches("WHERE ")
3138            );
3139            bound_values.extend(filter_values);
3140        }
3141    }
3142
3143    let source = format!(
3144        "{}.{} AS {}",
3145        postrust_sql::escape_ident(&lookup.schema_name),
3146        postrust_sql::escape_ident(&lookup.table_name),
3147        postrust_sql::escape_ident(READ_ROW)
3148    );
3149    let source_rows = format!(
3150        "SELECT {}.* FROM {}{}",
3151        postrust_sql::escape_ident(READ_ROW),
3152        source,
3153        where_sql
3154    );
3155    let mut projection = {
3156        let guard = gql_ctx
3157            .schema_cache
3158            .get()
3159            .await
3160            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3161        guard
3162            .as_ref()
3163            .and_then(|cache| {
3164                cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3165                    &lookup.schema_name,
3166                    &lookup.table_name,
3167                ))
3168            })
3169            .and_then(|table| rename_projection(table, "src", state.names.as_ref()))
3170            .unwrap_or_else(|| "src.*".to_string())
3171    };
3172    let projected_rows = {
3173        let guard = gql_ctx
3174            .schema_cache
3175            .get()
3176            .await
3177            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3178        let cache = guard
3179            .as_ref()
3180            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3181        let table = cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3182            &lookup.schema_name,
3183            &lookup.table_name,
3184        ));
3185        let selected_fields = entity_selection_fields(ctx, &lookup.type_name);
3186        let mut param_idx = bound_values.len() + 1;
3187        let computed = match table {
3188            Some(table) => computed_projections(
3189                table,
3190                selected_fields.iter().copied(),
3191                "src",
3192                state.names.as_ref(),
3193                cache,
3194                &mut param_idx,
3195                &mut bound_values,
3196            )?,
3197            None => Vec::new(),
3198        };
3199        let embeds = build_embed_expressions(
3200            &gql_ctx.caller(),
3201            cache,
3202            state.relationships.as_ref(),
3203            &lookup.type_name,
3204            "src",
3205            selected_fields,
3206            state.max_rows,
3207            &mut 0,
3208            &mut param_idx,
3209            &mut bound_values,
3210            state.names.as_ref(),
3211        )?;
3212        for expression in &computed {
3213            projection.push_str(", ");
3214            projection.push_str(expression);
3215        }
3216        for (field_name, expression) in &embeds {
3217            projection.push_str(", ");
3218            projection.push_str(expression);
3219            projection.push_str(" AS ");
3220            projection.push_str(&postrust_sql::escape_ident(field_name));
3221        }
3222        format!("SELECT {} FROM ({}) AS src", projection, source_rows)
3223    };
3224    // PostgreSQL decides whether an entity key matches. Carry the input's
3225    // ordinal through that same query instead of reconstructing a key from
3226    // row JSON: PostgreSQL-equivalent spellings (UUID case, citext case and
3227    // timestamptz offsets, for example) need to resolve to the row it found.
3228    let sql = format!(
3229        "SELECT json_build_array({}.{}, {}) FROM {} CROSS JOIN LATERAL ({}) AS t",
3230        postrust_sql::escape_ident(representation_alias),
3231        postrust_sql::escape_ident(ordinality_column),
3232        row_json("t", &lookup.row_column_types),
3233        representations_sql,
3234        projected_rows,
3235    );
3236    let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3237    let rows = execute_query_on(&mut tx, &sql, &bound_values).await?;
3238    tx.commit().await?;
3239
3240    let mut by_ordinal = std::iter::repeat_with(|| None)
3241        .take(representations.len())
3242        .collect::<Vec<_>>();
3243    for result in rows {
3244        let serde_json::Value::Array(mut pair) = result else {
3245            return Err(async_graphql::Error::new(
3246                "entity query returned an invalid result",
3247            ));
3248        };
3249        if pair.len() != 2 {
3250            return Err(async_graphql::Error::new(
3251                "entity query returned an invalid result",
3252            ));
3253        }
3254        let row = pair.pop().expect("the result pair has two values");
3255        let ordinality = pair
3256            .pop()
3257            .and_then(|value| value.as_u64())
3258            .and_then(|value| usize::try_from(value).ok())
3259            .filter(|value| *value < by_ordinal.len())
3260            .ok_or_else(|| async_graphql::Error::new("entity query returned an invalid ordinal"))?;
3261        by_ordinal[ordinality] = Some(row);
3262    }
3263    Ok(by_ordinal)
3264}
3265
3266/// Validate an entity key and normalize explicit GraphQL `ID` overrides to
3267/// their integer, text or UUID storage type. `_Any` deliberately skips the
3268/// field-level checks ordinary GraphQL arguments receive.
3269fn normalized_entity_key_values(
3270    values: &serde_json::Map<String, serde_json::Value>,
3271    key_columns: &[EntityKeyColumn],
3272    type_name: &str,
3273) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
3274    let mut key = Vec::with_capacity(key_columns.len());
3275    for key_column in key_columns {
3276        let value = values.get(&key_column.field_name).ok_or_else(|| {
3277            async_graphql::Error::new(format!(
3278                "entity representation for \"{}\" is missing key field \"{}\"",
3279                type_name, key_column.field_name
3280            ))
3281        })?;
3282        key.push(normalize_entity_key_value(type_name, key_column, value)?);
3283    }
3284    Ok(key)
3285}
3286
3287fn normalize_entity_key_value(
3288    type_name: &str,
3289    key_column: &EntityKeyColumn,
3290    value: &serde_json::Value,
3291) -> Result<serde_json::Value, async_graphql::Error> {
3292    let invalid = |expected: &str| {
3293        async_graphql::Error::new(format!(
3294            "entity representation for \"{}\" has invalid key field \"{}\": expected {}, found {}",
3295            type_name,
3296            key_column.field_name,
3297            expected,
3298            json_value_kind(value)
3299        ))
3300    };
3301
3302    if key_column.explicit_id {
3303        return normalize_graphql_id(value, &key_column.pg_type).ok_or_else(|| {
3304            invalid(&format!(
3305                "an ID compatible with PostgreSQL type \"{}\"",
3306                key_column.pg_type
3307            ))
3308        });
3309    }
3310
3311    let pg_type = key_column.pg_type.trim().to_ascii_lowercase();
3312    let normalized = if matches!(pg_type.as_str(), "int8" | "bigint") {
3313        match value {
3314            serde_json::Value::Number(number) => number.as_i64(),
3315            serde_json::Value::String(text) => text.parse::<i64>().ok(),
3316            _ => None,
3317        }
3318        .map(serde_json::Value::from)
3319    } else if integer_bounds(&pg_type).is_some() {
3320        value.as_i64().and_then(|integer| {
3321            let (min, max) = integer_bounds(&pg_type)?;
3322            (min..=max)
3323                .contains(&integer)
3324                .then(|| serde_json::Value::from(integer))
3325        })
3326    } else if matches!(
3327        pg_type.as_str(),
3328        "float4" | "float8" | "real" | "double precision"
3329    ) {
3330        value
3331            .as_f64()
3332            .and_then(serde_json::Number::from_f64)
3333            .map(serde_json::Value::Number)
3334    } else if matches!(pg_type.as_str(), "numeric" | "decimal") {
3335        match value {
3336            serde_json::Value::Number(number) => {
3337                Some(serde_json::Value::String(number.to_string()))
3338            }
3339            serde_json::Value::String(text) if text.parse::<sqlx::types::BigDecimal>().is_ok() => {
3340                Some(serde_json::Value::String(text.clone()))
3341            }
3342            _ => None,
3343        }
3344    } else if matches!(pg_type.as_str(), "bool" | "boolean") {
3345        value.as_bool().map(serde_json::Value::Bool)
3346    } else if matches!(pg_type.as_str(), "json" | "jsonb") {
3347        Some(value.clone())
3348    } else if is_array_type(&pg_type) {
3349        value.is_array().then(|| value.clone())
3350    } else {
3351        value
3352            .as_str()
3353            .map(|text| serde_json::Value::String(text.to_string()))
3354    };
3355
3356    normalized.ok_or_else(|| {
3357        invalid(&format!(
3358            "a value compatible with PostgreSQL type \"{}\"",
3359            key_column.pg_type
3360        ))
3361    })
3362}
3363
3364fn normalize_graphql_id(value: &serde_json::Value, pg_type: &str) -> Option<serde_json::Value> {
3365    if integer_bounds(pg_type).is_some() {
3366        let integer = match value {
3367            serde_json::Value::Number(number) => number.as_i64(),
3368            serde_json::Value::String(text) => text.parse::<i64>().ok(),
3369            _ => None,
3370        }?;
3371        let (min, max) = integer_bounds(pg_type)?;
3372        return (min..=max)
3373            .contains(&integer)
3374            .then(|| serde_json::Value::from(integer));
3375    }
3376    if pg_type.eq_ignore_ascii_case("uuid") {
3377        return normalize_uuid(value).map(serde_json::Value::String);
3378    }
3379    if is_text_type(pg_type) {
3380        return match value {
3381            serde_json::Value::String(s) => Some(serde_json::Value::String(s.clone())),
3382            serde_json::Value::Number(n) if n.is_i64() || n.is_u64() => {
3383                Some(serde_json::Value::String(n.to_string()))
3384            }
3385            _ => None,
3386        };
3387    }
3388    None
3389}
3390
3391fn integer_bounds(pg_type: &str) -> Option<(i64, i64)> {
3392    match pg_type.trim().to_ascii_lowercase().as_str() {
3393        "int2" | "smallint" => Some((i16::MIN.into(), i16::MAX.into())),
3394        "int4" | "int" | "integer" => Some((i32::MIN.into(), i32::MAX.into())),
3395        "int8" | "bigint" => Some((i64::MIN, i64::MAX)),
3396        _ => None,
3397    }
3398}
3399
3400fn is_text_type(pg_type: &str) -> bool {
3401    matches!(
3402        pg_type.trim().to_ascii_lowercase().as_str(),
3403        "text" | "varchar" | "character varying" | "char" | "character" | "bpchar" | "citext"
3404    )
3405}
3406
3407fn normalize_uuid(value: &serde_json::Value) -> Option<String> {
3408    sqlx::types::Uuid::parse_str(value.as_str()?)
3409        .ok()
3410        .map(|uuid| uuid.to_string())
3411}
3412
3413fn json_value_kind(value: &serde_json::Value) -> &'static str {
3414    match value {
3415        serde_json::Value::Null => "null",
3416        serde_json::Value::Bool(_) => "a boolean",
3417        serde_json::Value::Number(_) => "a number",
3418        serde_json::Value::String(_) => "a string",
3419        serde_json::Value::Array(_) => "an array",
3420        serde_json::Value::Object(_) => "an object",
3421    }
3422}
3423
3424/// Add the root fields for functions that answer with rows of a table.
3425///
3426/// The same arguments a table's own root field takes, because the rows are the
3427/// same rows: what the function adds is `args`, which is where its own
3428/// arguments go so that a parameter called `limit` cannot shadow the one that
3429/// pages the result.
3430fn add_function_fields(
3431    mut root: Object,
3432    generated: &GeneratedSchema,
3433    volatile: bool,
3434    max_rows: Option<i64>,
3435    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3436    names: Arc<crate::names::NameOverrides>,
3437) -> Object {
3438    // The names the root already carries, from the tables. A function may be
3439    // named after one -- `create function t() returns setof t` is an ordinary
3440    // way to write a parameterised view -- and `Object::field` *panics* on a
3441    // duplicate, so an unguarded collision is a server that will not start.
3442    let taken: HashSet<&str> = if volatile {
3443        generated
3444            .mutation_fields
3445            .iter()
3446            .map(|f| f.name.as_str())
3447            .collect()
3448    } else {
3449        generated
3450            .query_fields
3451            .iter()
3452            .map(|f| f.name.as_str())
3453            .collect()
3454    };
3455
3456    for function in &generated.function_fields {
3457        if function.volatile != volatile {
3458            continue;
3459        }
3460        if taken.contains(function.name.as_str()) {
3461            tracing::warn!(
3462                "not exposing {}.{}({}) -- a table already has a root field \
3463                 called {}",
3464                function.schema_name,
3465                function.function_name,
3466                function.argument_signature(),
3467                function.name,
3468            );
3469            continue;
3470        }
3471        let spec = Arc::new(QueryFieldSpec {
3472            schema_name: function.returns_table.0.clone(),
3473            table_name: function.returns_table.1.clone(),
3474            type_name: function.returns.clone(),
3475            is_by_pk: false,
3476            pk_columns: Vec::new(),
3477            max_rows,
3478            relationships: Arc::clone(&relationships),
3479            names: Arc::clone(&names),
3480            call: Some(Arc::new(FunctionCall {
3481                schema: function.schema_name.clone(),
3482                name: function.function_name.clone(),
3483                arguments: function.arguments.clone(),
3484                session_argument: function.session_argument.clone(),
3485            })),
3486        });
3487
3488        let mut field = Field::new(
3489            &function.name,
3490            TypeRef::named_nn_list_nn(&function.returns),
3491            move |ctx| {
3492                let spec = Arc::clone(&spec);
3493                FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
3494            },
3495        );
3496        if !function.arguments.is_empty() {
3497            field = field.argument(InputValue::new(
3498                "args",
3499                TypeRef::named_nn(format!("{}_args", function.name)),
3500            ));
3501        }
3502        field = field
3503            .argument(InputValue::new(
3504                "where",
3505                TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
3506                    &function.local_returns,
3507                )),
3508            ))
3509            .argument(InputValue::new(
3510                "order_by",
3511                TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
3512                    &function.local_returns,
3513                )),
3514            ))
3515            .argument(InputValue::new(
3516                "distinct_on",
3517                TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
3518                    &function.local_returns,
3519                )),
3520            ))
3521            .argument(InputValue::new("limit", TypeRef::named("Int")))
3522            .argument(InputValue::new("offset", TypeRef::named("Int")));
3523        if let Some(description) = &function.description {
3524            field = field.description(description);
3525        }
3526        root = root.field(field);
3527
3528        // The same rows, with numbers about them. Every table root has one and
3529        // so does every function root: `search_tracks_aggregate(args: {…})`
3530        // counts what `search_tracks(args: {…})` would have answered with,
3531        // which means calling the function and taking the same arguments.
3532        let agg_spec = Arc::new(AggregateSpec {
3533            schema_name: function.returns_table.0.clone(),
3534            table_name: function.returns_table.1.clone(),
3535            type_name: function.returns.clone(),
3536            max_rows,
3537            relationships: Arc::clone(&relationships),
3538            names: Arc::clone(&names),
3539            call: Some(Arc::new(FunctionCall {
3540                schema: function.schema_name.clone(),
3541                name: function.function_name.clone(),
3542                arguments: function.arguments.clone(),
3543                session_argument: function.session_argument.clone(),
3544            })),
3545        });
3546        let mut aggregate = Field::new(
3547            format!("{}_aggregate", function.name),
3548            TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
3549                &function.local_returns,
3550            )),
3551            move |ctx| {
3552                let agg_spec = Arc::clone(&agg_spec);
3553                FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
3554            },
3555        );
3556        if !function.arguments.is_empty() {
3557            aggregate = aggregate.argument(InputValue::new(
3558                "args",
3559                TypeRef::named_nn(format!("{}_args", function.name)),
3560            ));
3561        }
3562        aggregate = with_row_arguments(aggregate, &function.local_returns).description(format!(
3563            "fetch aggregated fields from the table: \"{}\"",
3564            function.returns_table.1
3565        ));
3566        root = root.field(aggregate);
3567    }
3568    root
3569}
3570
3571/// The FROM entry for a root field that calls a function.
3572///
3573/// Aliased as the table the function answers with rows of, so a filter or an
3574/// ordering written against that table's columns finds them: without it the
3575/// FROM clause names the function and `article.score` is a table nobody
3576/// mentioned. Whatever the caller put under `args` is bound here, which is why
3577/// it takes the parameter list.
3578fn function_source(
3579    ctx: &ResolverContext<'_>,
3580    call: &FunctionCall,
3581    alias: &str,
3582    bound_values: &mut Vec<serde_json::Value>,
3583) -> Result<String, async_graphql::Error> {
3584    let given = ctx
3585        .args
3586        .try_get("args")
3587        .ok()
3588        .map(|v| accessor_to_json(&v))
3589        .unwrap_or_else(|| serde_json::json!({}));
3590    let mut passed = Vec::new();
3591    for (name, _, required) in &call.arguments {
3592        match given.get(name) {
3593            None if *required => {
3594                return Err(async_graphql::Error::new(format!(
3595                    "{} needs the argument \"{}\"",
3596                    call.name, name
3597                )))
3598            }
3599            // An argument left out is left out, so the function's own default
3600            // applies. Passing null instead would override it.
3601            None => continue,
3602            Some(value) => {
3603                // Named notation, so the arguments a client did give land on
3604                // the parameters it meant regardless of order. A GraphQL Int
3605                // binds as a bigint, and `f(article_id => bigint)` is not
3606                // `f(integer)` -- the function is looked up by the types of
3607                // what it was handed, so what it was handed has to be the
3608                // types it declares.
3609                let (_, pg_type, _) = call
3610                    .arguments
3611                    .iter()
3612                    .find(|(argument, _, _)| argument == name)
3613                    .expect("iterating the arguments");
3614                passed.push(format!(
3615                    "{} => ${}::{}",
3616                    postrust_sql::escape_ident(name),
3617                    bound_values.len() + 1,
3618                    pg_type
3619                ));
3620                bound_values.push(value.clone());
3621            }
3622        }
3623    }
3624    // The session argument is not the client's to send: it says who is
3625    // asking, and a caller that could write it could name any identity it
3626    // liked. It is filled here, from the verified token.
3627    if let Some(session) = &call.session_argument {
3628        passed.push(format!(
3629            "{} => {}",
3630            postrust_sql::escape_ident(session),
3631            SESSION_ARGUMENT
3632        ));
3633    }
3634    Ok(format!(
3635        "{}.{}({}) AS {}",
3636        postrust_sql::escape_ident(&call.schema),
3637        postrust_sql::escape_ident(&call.name),
3638        passed.join(", "),
3639        postrust_sql::escape_ident(alias)
3640    ))
3641}
3642
3643/// A function a root field calls instead of reading a table.
3644struct FunctionCall {
3645    schema: String,
3646    name: String,
3647    arguments: Vec<(String, String, bool)>,
3648    /// The parameter filled from the caller's session rather than from the
3649    /// request. See [`crate::schema::FunctionField::session_argument`].
3650    session_argument: Option<String>,
3651}
3652
3653/// Everything an aggregate field's resolver needs.
3654struct AggregateSpec {
3655    schema_name: String,
3656    table_name: String,
3657    type_name: String,
3658    max_rows: Option<i64>,
3659    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3660    names: Arc<crate::names::NameOverrides>,
3661    /// Set where the rows come from a function rather than from the table
3662    /// itself, exactly as it is on [`QueryFieldSpec`]. Counting the rows a
3663    /// function answers with means calling it.
3664    call: Option<Arc<FunctionCall>>,
3665}
3666
3667/// Resolve an aggregate field.
3668///
3669/// One pass over the selection decides what SQL to write: a client asking only
3670/// for `count` should not pay for the rows, and one asking only for `nodes`
3671/// should not pay for a second scan. Both halves read from the same filtered
3672/// subquery, so `aggregate` describes exactly the set `nodes` came from.
3673async fn resolve_aggregate<'a>(
3674    ctx: &ResolverContext<'a>,
3675    spec: &AggregateSpec,
3676) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3677    Ok(Some(FieldValue::value(aggregate_value(ctx, spec).await?)))
3678}
3679
3680/// The numbers an aggregate field answers with.
3681///
3682/// Split from the resolver for the reason [`query_rows`] is: a subscription
3683/// reads them over and over and has to tell one answer from the last.
3684async fn aggregate_value(
3685    ctx: &ResolverContext<'_>,
3686    spec: &AggregateSpec,
3687) -> Result<Value, async_graphql::Error> {
3688    use crate::schema::aggregate as agg;
3689
3690    let pool = ctx.data::<PgPool>()?;
3691    let gql_ctx = ctx.data::<GraphQLContext>()?;
3692
3693    let mut bound_values: Vec<serde_json::Value> = Vec::new();
3694    // A function's own arguments are bound before anything else, so a
3695    // predicate written beside them continues the numbering rather than
3696    // colliding with it.
3697    let source = match &spec.call {
3698        None => format!(
3699            "{}.{}",
3700            postrust_sql::escape_ident(&spec.schema_name),
3701            postrust_sql::escape_ident(&spec.table_name)
3702        ),
3703        Some(call) => function_source(ctx, call, &spec.table_name, &mut bound_values)?,
3704    };
3705
3706    let mut where_sql = String::new();
3707    {
3708        // Counting rows is reading them, so the permission applies here in the
3709        // same breath as the request's own filter -- otherwise `count` would
3710        // report a total the same role cannot list.
3711        let requested = ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v));
3712        let permission = permission_predicate(
3713            &gql_ctx.caller(),
3714            spec.names.as_ref(),
3715            &spec.schema_name,
3716            &spec.table_name,
3717        )?;
3718        if let Some(predicate) = and_predicates(requested, permission) {
3719            let guard = gql_ctx
3720                .schema_cache
3721                .get()
3722                .await
3723                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3724            let cache = guard
3725                .as_ref()
3726                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3727            // A function's rows are read under the table's name as an alias,
3728            // not from the table itself -- the same split the row read makes.
3729            // Counting them under the qualified name answered `invalid
3730            // reference to FROM-clause entry for table "Track"` the moment a
3731            // permission referred to a column, which is any permission that
3732            // follows a relationship.
3733            let scope = match &spec.call {
3734                None => WhereScope::table(
3735                    &spec.schema_name,
3736                    &spec.table_name,
3737                    &spec.type_name,
3738                    spec.names.as_ref(),
3739                )
3740                .with_resolution(cache, spec.relationships.as_ref()),
3741                Some(_) => WhereScope::for_alias(
3742                    &spec.schema_name,
3743                    &spec.table_name,
3744                    &spec.table_name,
3745                    &spec.type_name,
3746                    cache,
3747                    spec.relationships.as_ref(),
3748                    spec.names.as_ref(),
3749                ),
3750            }
3751            .for_caller(gql_ctx.caller());
3752            let (sql, values) =
3753                build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3754            if !sql.is_empty() {
3755                where_sql = format!(" {}", sql);
3756                bound_values.extend(values);
3757            }
3758        }
3759    }
3760
3761    let order_sql = build_order_by_clause(
3762        ctx,
3763        &gql_ctx.schema_cache,
3764        &spec.schema_name,
3765        &spec.table_name,
3766        &spec.type_name,
3767        spec.relationships.as_ref(),
3768        &format!(
3769            "{}.{}",
3770            postrust_sql::escape_ident(&spec.schema_name),
3771            postrust_sql::escape_ident(&spec.table_name)
3772        ),
3773        spec.names.as_ref(),
3774    )
3775    .await?;
3776
3777    let requested_limit = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
3778    let offset = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
3779    let ceiling = match (
3780        spec.max_rows,
3781        permission_limit(
3782            &gql_ctx.caller(),
3783            spec.names.as_ref(),
3784            &spec.schema_name,
3785            &spec.table_name,
3786        ),
3787    ) {
3788        (Some(configured), Some(granted)) => Some(configured.min(granted)),
3789        (only, None) | (None, only) => only,
3790    };
3791    let limit = match (requested_limit, ceiling) {
3792        (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
3793        (Some(requested), None) => Some(requested),
3794        (None, ceiling) => ceiling,
3795    };
3796
3797    let rows = format!("SELECT * FROM {}{}{}", source, where_sql, order_sql);
3798
3799    // What `nodes` reads: the page, bounded by whichever ceiling is lowest.
3800    let mut inner = rows.clone();
3801    if let Some(limit) = limit {
3802        inner.push_str(&format!(" LIMIT {}", limit));
3803    }
3804    if let Some(offset) = offset {
3805        inner.push_str(&format!(" OFFSET {}", offset));
3806    }
3807
3808    // What `aggregate` reads, which is not the same rows. A count is not a
3809    // page: a ceiling exists to bound how many rows travel, and answering
3810    // "how many are there" with "as many as I would have sent you" is not an
3811    // answer to the question. Hasura says so too -- a role limited to one row
3812    // of `article` still counts three of them and still gets `max(id)` over
3813    // all three, which is what `agg_perm` tests.
3814    //
3815    // The request's own `limit` and `offset` do still apply: those were asked
3816    // for, and `article_aggregate(limit: 2)` is a question about two rows. The
3817    // corpus proves this only for the permission's ceiling; the configured one
3818    // is treated the same way because the reason is the same, and because a
3819    // server whose `PGRST_MAX_ROWS` silently became the answer to `count`
3820    // would be wrong in the way that is hardest to notice.
3821    let mut counted = rows;
3822    if let Some(limit) = requested_limit {
3823        counted.push_str(&format!(" LIMIT {}", limit));
3824    }
3825    if let Some(offset) = offset {
3826        counted.push_str(&format!(" OFFSET {}", offset));
3827    }
3828
3829    // What the client actually asked for. Two selections of one aggregate are
3830    // one entry: `sum { id }` beside `totals: sum { views }` reads the same
3831    // object under two names, so the columns are the union of what both named
3832    // and the object is built once.
3833    let mut wants_nodes = false;
3834    let mut wants_aggregate = false;
3835    let mut wanted: BTreeMap<String, Vec<String>> = BTreeMap::new();
3836    // `count` is different: each occurrence may count something else, so each
3837    // is answered under the name it was asked for.
3838    let mut counts: Vec<(String, String)> = Vec::new();
3839    for selection in ctx.field().selection_set() {
3840        match selection.name() {
3841            "nodes" => wants_nodes = true,
3842            "aggregate" => {
3843                wants_aggregate = true;
3844                for function in selection.selection_set() {
3845                    // Answered by the schema, at this level and the one below.
3846                    if function.name() == "__typename" {
3847                        continue;
3848                    }
3849                    if function.name() == "count" {
3850                        counts.push((
3851                            function.alias().unwrap_or("count").to_string(),
3852                            count_expression(
3853                                function,
3854                                (&spec.schema_name, &spec.table_name),
3855                                None,
3856                                spec.names.as_ref(),
3857                            ),
3858                        ));
3859                        continue;
3860                    }
3861                    // Entered even when it names no column: `max { __typename }`
3862                    // is an object with nothing in it, not a null.
3863                    let columns = wanted.entry(function.name().to_string()).or_default();
3864                    for column in function.selection_set() {
3865                        let name = column.name().to_string();
3866                        if name != "__typename" && !columns.contains(&name) {
3867                            columns.push(name);
3868                        }
3869                    }
3870                }
3871            }
3872            _ => {}
3873        }
3874    }
3875
3876    let mut result = async_graphql::indexmap::IndexMap::new();
3877
3878    // Whatever `aggregate` names -- even if that is only `__typename` -- it is
3879    // an object, so it is answered rather than left null.
3880    if wants_aggregate {
3881        // `count` is answered whether or not it was asked for: the aggregate
3882        // type's own resolver reads a missing key as zero, and a client that
3883        // asked for nothing else still gets a number rather than a null.
3884        let mut parts = match counts.iter().any(|(key, _)| key == "count") {
3885            true => Vec::new(),
3886            false => vec!["'count', count(*)".to_string()],
3887        };
3888        for (key, expression) in &counts {
3889            parts.push(format!(
3890                "{}, {}",
3891                postrust_sql::quote_literal(key),
3892                expression
3893            ));
3894        }
3895        for (function, columns) in &wanted {
3896            // The function name comes from the schema, not from the request:
3897            // a selection can only name a field that was generated, so there
3898            // is no path from client text to this identifier.
3899            let sql_function = agg::NUMERIC_AGGREGATES
3900                .iter()
3901                .chain(agg::ORDERED_AGGREGATES.iter())
3902                .find(|(name, _)| name == function)
3903                .map(|(name, _)| *name)
3904                .ok_or_else(|| {
3905                    async_graphql::Error::new(format!("unknown aggregate \"{}\"", function))
3906                })?;
3907            let per_column: Vec<String> = columns
3908                .iter()
3909                .map(|field| {
3910                    let source = spec
3911                        .names
3912                        .column_source(&spec.schema_name, &spec.table_name, field)
3913                        .unwrap_or(field);
3914                    format!(
3915                        "{}, {}({})",
3916                        postrust_sql::quote_literal(field),
3917                        sql_function,
3918                        postrust_sql::escape_ident(source)
3919                    )
3920                })
3921                .collect();
3922            parts.push(format!(
3923                "'{}', json_build_object({})",
3924                sql_function,
3925                per_column.join(", ")
3926            ));
3927        }
3928
3929        let sql = format!(
3930            "SELECT json_build_object({}) FROM ({}) AS pgrst_agg",
3931            parts.join(", "),
3932            counted
3933        );
3934        let mut conn =
3935            begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3936        let rows = execute_query_on(&mut conn, &sql, &bound_values).await?;
3937        conn.commit().await?;
3938        if let Some(first) = rows.into_iter().next() {
3939            if let Value::Object(map) = json_to_value(first) {
3940                result.insert(async_graphql::Name::new("aggregate"), Value::Object(map));
3941            }
3942        }
3943    }
3944
3945    if wants_nodes {
3946        // `nodes` is the same rows selection the plain root field answers, so
3947        // it gets the same projection: computed fields, which are functions of
3948        // the row rather than part of it, and relationships, which are
3949        // correlated subselects. Reading it as columns alone was why a
3950        // relationship asked for beside a count came back null.
3951        let nodes = ctx
3952            .field()
3953            .selection_set()
3954            .find(|selection| selection.name() == "nodes")
3955            .ok_or_else(|| async_graphql::Error::new("nodes was asked for and then was not"))?;
3956
3957        // The aggregate query has already been sent, so its parameters are
3958        // fixed; anything an embed binds continues that numbering in a vector
3959        // of its own.
3960        let mut node_values = bound_values.clone();
3961        let (projection, row_column_types) = {
3962            let guard = gql_ctx
3963                .schema_cache
3964                .get()
3965                .await
3966                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3967            let cache = guard
3968                .as_ref()
3969                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3970            let qi = postrust_core::api_request::QualifiedIdentifier::new(
3971                &spec.schema_name,
3972                &spec.table_name,
3973            );
3974            let table = cache.get_table(&qi);
3975            let mut param_idx = node_values.len() + 1;
3976            let computed = match table {
3977                Some(table) => computed_projections(
3978                    table,
3979                    nodes.selection_set(),
3980                    "src",
3981                    spec.names.as_ref(),
3982                    cache,
3983                    &mut param_idx,
3984                    &mut node_values,
3985                )?,
3986                None => Vec::new(),
3987            };
3988            let embeds = build_embed_expressions(
3989                &gql_ctx.caller(),
3990                cache,
3991                spec.relationships.as_ref(),
3992                &spec.type_name,
3993                "src",
3994                nodes.selection_set(),
3995                spec.max_rows,
3996                &mut 0,
3997                &mut param_idx,
3998                &mut node_values,
3999                spec.names.as_ref(),
4000            )?;
4001            // A renamed column is renamed here, in the projection over the
4002            // subquery -- not inside it, where `src` has to stay the table's
4003            // own composite for a computed field to be passed the row.
4004            let mut projection = table
4005                .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4006                .unwrap_or_else(|| "src.*".to_string());
4007            for expression in &computed {
4008                projection.push_str(", ");
4009                projection.push_str(expression);
4010            }
4011            for (field_name, expression) in &embeds {
4012                projection.push_str(", ");
4013                projection.push_str(expression);
4014                projection.push_str(" AS ");
4015                projection.push_str(&postrust_sql::escape_ident(field_name));
4016            }
4017            (
4018                projection,
4019                table
4020                    .map(|table| exposed_column_types(table, spec.names.as_ref()))
4021                    .unwrap_or_default(),
4022            )
4023        };
4024
4025        let sql = format!(
4026            "SELECT {} FROM (SELECT {} FROM ({}) AS src) AS pgrst_nodes",
4027            row_json("pgrst_nodes", &row_column_types),
4028            projection,
4029            inner
4030        );
4031        let mut conn =
4032            begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
4033        let rows = execute_query_on(&mut conn, &sql, &node_values).await?;
4034        conn.commit().await?;
4035        result.insert(
4036            async_graphql::Name::new("nodes"),
4037            Value::List(rows.into_iter().map(json_to_value).collect()),
4038        );
4039    }
4040
4041    let _ = &spec.type_name;
4042    Ok(Value::Object(result))
4043}
4044
4045/// Resolve a query field.
4046async fn resolve_query<'a>(
4047    ctx: &ResolverContext<'a>,
4048    spec: &QueryFieldSpec,
4049) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4050    Ok(rows_as_field_value(
4051        query_rows(ctx, spec).await?,
4052        spec.is_by_pk,
4053    ))
4054}
4055
4056/// The rows a query field answers with, as JSON.
4057///
4058/// Split from the field's own resolver because a subscription reads the same
4059/// rows over and over and has to tell one answer from the last: a
4060/// `FieldValue` is neither comparable nor readable, and this is.
4061async fn query_rows(
4062    ctx: &ResolverContext<'_>,
4063    spec: &QueryFieldSpec,
4064) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4065    let schema_name = spec.schema_name.as_str();
4066    let table_name = spec.table_name.as_str();
4067    let type_name = spec.type_name.as_str();
4068    let is_by_pk = spec.is_by_pk;
4069    let pk_columns = spec.pk_columns.as_slice();
4070    let max_rows = spec.max_rows;
4071    let relationships = spec.relationships.as_ref();
4072
4073    let pool = ctx.data::<PgPool>()?;
4074    let gql_ctx = ctx.data::<GraphQLContext>()?;
4075
4076    debug!("Resolving query for table: {}", table_name);
4077
4078    // Extract pagination arguments
4079    let requested_limit: Option<i64> = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
4080
4081    let offset: Option<i64> = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
4082
4083    // A query that names no limit would otherwise select the whole table, so
4084    // the configured ceiling is applied as the limit in that case, and as an
4085    // upper bound when the query asks for more than it. A by-PK query resolves
4086    // to at most one row.
4087    let limit: Option<i64> = if is_by_pk {
4088        Some(1)
4089    } else {
4090        // The permission's ceiling is one more of the same kind, so it folds
4091        // in the same way: whichever of the three is smallest wins, and a
4092        // request naming none takes the lowest that was named.
4093        let ceiling = match (
4094            max_rows,
4095            permission_limit(
4096                &gql_ctx.caller(),
4097                spec.names.as_ref(),
4098                schema_name,
4099                table_name,
4100            ),
4101        ) {
4102            (Some(configured), Some(granted)) => Some(configured.min(granted)),
4103            (only, None) | (None, only) => only,
4104        };
4105        match (requested_limit, ceiling) {
4106            (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
4107            (Some(requested), None) => Some(requested),
4108            (None, ceiling) => ceiling,
4109        }
4110    };
4111
4112    // Build the WHERE clause.
4113    //
4114    // A by-PK query filters on the table's key columns; each value is bound as
4115    // a parameter and cast to the column's type, since GraphQL scalars and
4116    // PostgreSQL types do not line up (a `uuid` key arrives as a String). A
4117    // list query filters on the `filter` argument, which takes the same shape
4118    // as a mutation's `where`.
4119    let mut where_sql = String::new();
4120    let mut bound_values: Vec<serde_json::Value> = Vec::new();
4121
4122    // Where the rows come from. A function's own arguments are bound before
4123    // anything else, because they sit in the FROM clause and every other
4124    // parameter is numbered after them.
4125    let source = match &spec.call {
4126        // Aliased rather than named. A whole-row reference -- what a computed
4127        // field is passed -- can only be written as a bare name, and
4128        // `"public"."author"` is not one: PostgreSQL reads it as a column of a
4129        // table called `public`. An alias no column can share is a name that
4130        // works in both positions.
4131        None => format!(
4132            "{}.{} AS {}",
4133            postrust_sql::escape_ident(schema_name),
4134            postrust_sql::escape_ident(table_name),
4135            postrust_sql::escape_ident(READ_ROW)
4136        ),
4137        Some(call) => function_source(ctx, call, table_name, &mut bound_values)?,
4138    };
4139
4140    // How a column of the source is referred to: a table by its qualified
4141    // name, a function's rows by the alias above.
4142    let source_ref = match &spec.call {
4143        None => postrust_sql::escape_ident(READ_ROW),
4144        Some(_) => postrust_sql::escape_ident(table_name),
4145    };
4146
4147    if is_by_pk {
4148        if pk_columns.is_empty() {
4149            return Err(async_graphql::Error::new(format!(
4150                "\"{}\" has no primary key, so it cannot be queried by key",
4151                table_name
4152            )));
4153        }
4154
4155        let mut conditions = Vec::with_capacity(pk_columns.len());
4156        for (idx, (col_name, pg_type)) in pk_columns.iter().enumerate() {
4157            let field = spec
4158                .names
4159                .column(schema_name, table_name, col_name)
4160                .unwrap_or(col_name);
4161            let value = ctx.args.try_get(field).map_err(|_| {
4162                async_graphql::Error::new(format!(
4163                    "missing required primary key argument \"{}\"",
4164                    field
4165                ))
4166            })?;
4167
4168            conditions.push(format!(
4169                "{} = ${}::{}",
4170                postrust_sql::escape_ident(col_name),
4171                idx + 1,
4172                pg_type
4173            ));
4174            bound_values.push(accessor_to_json(&value));
4175        }
4176        where_sql = format!(" WHERE {}", conditions.join(" AND "));
4177    }
4178
4179    // What this role may read, beside what the request asked for. Applied to
4180    // both shapes and to neither: a query naming no `where` at all is still a
4181    // read, and a by-key query that skipped the permission would be the one
4182    // way to fetch a row the filter withholds.
4183    {
4184        let requested = match is_by_pk {
4185            true => None,
4186            false => ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v)),
4187        };
4188        let permission = permission_predicate(
4189            &gql_ctx.caller(),
4190            spec.names.as_ref(),
4191            schema_name,
4192            table_name,
4193        )?;
4194
4195        if let Some(predicate) = and_predicates(requested, permission) {
4196            let guard = gql_ctx
4197                .schema_cache
4198                .get()
4199                .await
4200                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4201            let cache = guard
4202                .as_ref()
4203                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4204            let scope = match &spec.call {
4205                None => WhereScope::table(schema_name, table_name, type_name, spec.names.as_ref())
4206                    .under_alias(READ_ROW)
4207                    .with_resolution(cache, relationships)
4208                    .for_caller(gql_ctx.caller()),
4209                // A function's rows are the table's rows, so they are renamed
4210                // the same way; only how they are reached differs.
4211                Some(_) => WhereScope::for_alias(
4212                    schema_name,
4213                    table_name,
4214                    table_name,
4215                    type_name,
4216                    cache,
4217                    relationships,
4218                    spec.names.as_ref(),
4219                )
4220                .for_caller(gql_ctx.caller()),
4221            };
4222            let (filter_sql, filter_values) =
4223                build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
4224            if !filter_sql.is_empty() {
4225                // A by-key query already has a `WHERE`, so this joins it
4226                // rather than replacing it.
4227                where_sql = match where_sql.is_empty() {
4228                    true => format!(" {}", filter_sql),
4229                    false => format!(
4230                        "{} AND ({})",
4231                        where_sql,
4232                        filter_sql.trim_start_matches("WHERE ")
4233                    ),
4234                };
4235                bound_values.extend(filter_values);
4236            }
4237        }
4238    }
4239
4240    // A by-key query resolves to one row, so neither ordering nor distinct
4241    // has anything to do.
4242    let (order_sql, distinct_on) = if is_by_pk {
4243        (String::new(), Vec::new())
4244    } else {
4245        (
4246            build_order_by_clause(
4247                ctx,
4248                &gql_ctx.schema_cache,
4249                schema_name,
4250                table_name,
4251                type_name,
4252                relationships,
4253                &source_ref,
4254                spec.names.as_ref(),
4255            )
4256            .await?,
4257            build_distinct_on(
4258                ctx,
4259                &gql_ctx.schema_cache,
4260                schema_name,
4261                table_name,
4262                spec.names.as_ref(),
4263            )
4264            .await?,
4265        )
4266    };
4267
4268    // PostgreSQL keeps the first row of each DISTINCT ON group in the query's
4269    // own order, and picks arbitrarily where the ordering does not begin with
4270    // the distinct columns -- so which row survives would depend on the plan.
4271    // Hasura refuses that query rather than answering it, and so does this:
4272    // prepending the distinct columns instead produced an answer, and a wrong
4273    // one, since `ORDER BY "department", "department" DESC` is decided by its
4274    // first term and sorts ascending.
4275    let (distinct_sql, order_sql) = {
4276        let written = order_sql.strip_prefix(" ORDER BY ");
4277        // Reported against the field's arguments as a whole: the rule is about
4278        // two of them disagreeing, so neither one is where it went wrong.
4279        let (prefix, order) = distinct_on_clause(&distinct_on, written).map_err(|error| {
4280            at_path(
4281                error,
4282                &format!(
4283                    "$.selectionSet.{}.args",
4284                    ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4285                ),
4286            )
4287        })?;
4288        (
4289            prefix,
4290            match order {
4291                Some(order) => format!(" ORDER BY {}", order),
4292                None => String::new(),
4293            },
4294        )
4295    };
4296
4297    // A computed column is a function of the row rather than part of it, so
4298    // it is not in `*` and is named only when it was asked for.
4299    let (computed, row_column_types) = {
4300        let guard = gql_ctx
4301            .schema_cache
4302            .get()
4303            .await
4304            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4305        match guard.as_ref() {
4306            Some(cache) => {
4307                let qi =
4308                    postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
4309                match cache.get_table(&qi) {
4310                    Some(table) => {
4311                        let mut param_idx = bound_values.len() + 1;
4312                        (
4313                            computed_projections(
4314                                table,
4315                                ctx.field().selection_set(),
4316                                "src",
4317                                spec.names.as_ref(),
4318                                cache,
4319                                &mut param_idx,
4320                                &mut bound_values,
4321                            )?,
4322                            exposed_column_types(table, spec.names.as_ref()),
4323                        )
4324                    }
4325                    None => (Vec::new(), HashMap::new()),
4326                }
4327            }
4328            None => (Vec::new(), HashMap::new()),
4329        }
4330    };
4331    // ORDER BY, LIMIT and OFFSET belong inside the subquery: applying them to
4332    // the outer `row_to_json` projection would leave the ordering of the rows
4333    // that survive the limit unspecified.
4334    let mut inner = format!(
4335        "SELECT {}* FROM {}{}{}",
4336        distinct_sql, source, where_sql, order_sql
4337    );
4338
4339    if let Some(limit) = limit {
4340        inner.push_str(&format!(" LIMIT {}", limit));
4341    }
4342
4343    if let Some(offset) = offset {
4344        inner.push_str(&format!(" OFFSET {}", offset));
4345    }
4346
4347    // Embed the requested relationships in this same query, as correlated
4348    // subselects in the SELECT list, so the whole selection is one round trip.
4349    let embed_expressions = {
4350        let guard = gql_ctx
4351            .schema_cache
4352            .get()
4353            .await
4354            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4355        match guard.as_ref() {
4356            Some(cache) => {
4357                // Parameters bound inside an embed continue the outer query's
4358                // numbering: sqlx binds by position, so the values only have
4359                // to be pushed in the order their placeholders were handed
4360                // out.
4361                let mut param_idx = bound_values.len() + 1;
4362                build_embed_expressions(
4363                    &gql_ctx.caller(),
4364                    cache,
4365                    relationships,
4366                    type_name,
4367                    "src",
4368                    ctx.field().selection_set(),
4369                    max_rows,
4370                    &mut 0,
4371                    &mut param_idx,
4372                    &mut bound_values,
4373                    spec.names.as_ref(),
4374                )?
4375            }
4376            None => Vec::new(),
4377        }
4378    };
4379
4380    // A computed column is a function of the row, and the row it is a function
4381    // of is the table's -- not the subquery's. Projecting it inside the
4382    // subquery gave `src` an extra column, which is what made a computed
4383    // relationship beside it fail with `cannot cast type record to author`: a
4384    // subquery alias is only passable as a composite value while its columns
4385    // are exactly the table's. So both live out here, where `src` still is
4386    // one.
4387    let renamed = {
4388        let guard = gql_ctx
4389            .schema_cache
4390            .get()
4391            .await
4392            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4393        guard
4394            .as_ref()
4395            .and_then(|cache| {
4396                cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
4397                    schema_name,
4398                    table_name,
4399                ))
4400            })
4401            .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4402    };
4403    let inner = if embed_expressions.is_empty() && computed.is_empty() && renamed.is_none() {
4404        inner
4405    } else {
4406        let mut projection = renamed.unwrap_or_else(|| "src.*".to_string());
4407        for expression in &computed {
4408            projection.push_str(", ");
4409            projection.push_str(expression);
4410        }
4411        for (field_name, expression) in &embed_expressions {
4412            projection.push_str(", ");
4413            projection.push_str(expression);
4414            projection.push_str(" AS ");
4415            projection.push_str(&postrust_sql::escape_ident(field_name));
4416        }
4417        format!("SELECT {} FROM ({}) AS src", projection, inner)
4418    };
4419
4420    let sql = format!(
4421        "SELECT {} FROM ({}) t",
4422        row_json("t", &row_column_types),
4423        inner
4424    );
4425
4426    // One transaction for the query and any embeds hanging off it, so the role
4427    // applies to all of them and the parent and child rows come from a single
4428    // snapshot.
4429    let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
4430
4431    // Execute query - returns Vec<serde_json::Value>
4432    let mut result = execute_query_on(&mut tx, &sql, &bound_values).await?;
4433
4434    // Anything the single-query form did not cover.
4435    if embed_expressions.is_empty() {
4436        let guard = gql_ctx
4437            .schema_cache
4438            .get()
4439            .await
4440            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4441        let cache = guard
4442            .as_ref()
4443            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4444
4445        let embed_ctx = EmbedContext {
4446            schema_cache: cache,
4447            relationships,
4448            max_rows,
4449        };
4450
4451        embed_relationships(&mut tx, &embed_ctx, type_name, ctx.field(), &mut result).await?;
4452    }
4453
4454    tx.commit().await?;
4455
4456    Ok(result)
4457}
4458
4459/// The rows a field answers with, in the shape its type says.
4460///
4461/// A by-key field is one row or none; every other is a list, empty where
4462/// nothing matched rather than null.
4463fn rows_as_field_value<'a>(rows: Vec<serde_json::Value>, is_by_pk: bool) -> Option<FieldValue<'a>> {
4464    match is_by_pk {
4465        true => rows
4466            .into_iter()
4467            .next()
4468            .map(|v| FieldValue::value(json_to_value(v))),
4469        false => Some(FieldValue::list(
4470            rows.into_iter()
4471                .map(|v| FieldValue::value(json_to_value(v))),
4472        )),
4473    }
4474}
4475
4476/// Resolve a mutation field.
4477#[allow(clippy::too_many_arguments)]
4478async fn resolve_mutation<'a>(
4479    ctx: &ResolverContext<'a>,
4480    schema_name: &str,
4481    table_name: &str,
4482    mutation_type: MutationType,
4483    pk_columns: &[(String, String)],
4484    relationships: &Arc<HashMap<String, Vec<RelationshipField>>>,
4485    type_names: &Arc<HashMap<(String, String), String>>,
4486    names: &Arc<crate::names::NameOverrides>,
4487    max_rows: Option<i64>,
4488) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4489    let pool = ctx.data::<PgPool>()?;
4490    let gql_ctx = ctx.data::<GraphQLContext>()?;
4491
4492    debug!(
4493        "Resolving mutation for table: {} type: {:?}",
4494        table_name, mutation_type
4495    );
4496
4497    // A relationship or a computed field asked for beside the written columns
4498    // is not in `RETURNING`, so the rows are read again through the projection
4499    // an ordinary query uses -- and only when the selection asks for something
4500    // that needs it. A delete cannot do that afterwards and builds it into the
4501    // statement instead, which is why this is settled before the statement is.
4502    let type_name = type_names
4503        .get(&(schema_name.to_string(), table_name.to_string()))
4504        .cloned()
4505        .unwrap_or_else(|| table_name.to_string());
4506
4507    let by_key = matches!(
4508        mutation_type,
4509        MutationType::InsertOne | MutationType::UpdateByPk | MutationType::DeleteByPk
4510    );
4511    let returning = if by_key {
4512        Some(ctx.field())
4513    } else {
4514        ctx.field()
4515            .selection_set()
4516            .find(|selection| selection.name() == "returning")
4517    };
4518
4519    // Several updates, each with its own filter, applied in the order given.
4520    // They share the operation's transaction like any other write, which is
4521    // the whole difference from sending them one at a time: either all of them
4522    // happened or none did.
4523    if mutation_type == MutationType::UpdateMany {
4524        let updates = ctx
4525            .args
4526            .try_get("updates")
4527            .ok()
4528            .map(|v| accessor_to_json(&v))
4529            .unwrap_or(serde_json::Value::Null);
4530        let serde_json::Value::Array(entries) = updates else {
4531            return Err(async_graphql::Error::new(
4532                "\"updates\" takes a list of {where, _set, …} objects",
4533            ));
4534        };
4535        let column_types = column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await;
4536        let mut answers: Vec<FieldValue<'_>> = Vec::with_capacity(entries.len());
4537        for entry in entries {
4538            let serde_json::Value::Object(entry) = entry else {
4539                return Err(async_graphql::Error::new(
4540                    "each update is an object of {where, _set, …}",
4541                ));
4542            };
4543            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4544                .iter()
4545                .filter_map(|name| entry.get(*name).map(|value| (*name, value.clone())))
4546                .filter(|(_, value)| !value.is_null())
4547                .collect();
4548            let rows = execute_update(
4549                pool,
4550                gql_ctx,
4551                schema_name,
4552                table_name,
4553                &type_name,
4554                operators,
4555                column_types.clone(),
4556                entry.get("where").cloned(),
4557                relationships.as_ref(),
4558                names.as_ref(),
4559            )
4560            .await?;
4561            let affected = rows.len();
4562            let rows = match returning {
4563                Some(returning) if !rows.is_empty() => {
4564                    reread_returning(
4565                        pool,
4566                        gql_ctx,
4567                        schema_name,
4568                        table_name,
4569                        &type_name,
4570                        rows,
4571                        returning,
4572                        relationships.as_ref(),
4573                        names.as_ref(),
4574                        max_rows,
4575                    )
4576                    .await?
4577                }
4578                _ => rows,
4579            };
4580            answers.extend(mutation_result(rows, affected, false));
4581        }
4582        return Ok(Some(FieldValue::list(answers)));
4583    }
4584
4585    let (result, affected) = match mutation_type {
4586        MutationType::Insert | MutationType::InsertOne => {
4587            let objects = ctx
4588                .args
4589                .try_get("objects")
4590                .ok()
4591                .map(|v| accessor_to_json(&v))
4592                .or_else(|| {
4593                    // `insert_x_one(object: {...})` is one row spelled without
4594                    // the list.
4595                    ctx.args
4596                        .try_get("object")
4597                        .ok()
4598                        .map(|v| serde_json::Value::Array(vec![accessor_to_json(&v)]))
4599                })
4600                .unwrap_or_else(|| serde_json::Value::Array(vec![]));
4601
4602            let guard = gql_ctx
4603                .schema_cache
4604                .get()
4605                .await
4606                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4607            let cache = guard
4608                .as_ref()
4609                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4610            // Where this write is, in the document the client sent. The
4611            // field as it was written -- an alias if there was one, since that
4612            // is what names this selection.
4613            let here = format!(
4614                "$.selectionSet.{}",
4615                ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4616            );
4617            // `insert_x(objects: [...])` against `insert_x_one(object: {...})`:
4618            // the same write under two spellings, and the path says which.
4619            let named = match mutation_type {
4620                MutationType::InsertOne => "object",
4621                _ => "objects",
4622            };
4623            let context = InsertContext {
4624                on_conflict: ctx
4625                    .args
4626                    .try_get("on_conflict")
4627                    .ok()
4628                    .map(|v| accessor_to_json(&v))
4629                    .filter(|v| !v.is_null()),
4630                cache,
4631                relationships: relationships.as_ref(),
4632                type_names: type_names.as_ref(),
4633                names: names.as_ref(),
4634                caller: gql_ctx.caller(),
4635                // Filled in per row by `execute_insert`, which is where the
4636                // index is known.
4637                row: String::new(),
4638                objects: format!("{}.args.{}", here, named),
4639                conflict: format!("{}.args.on_conflict", here),
4640            };
4641
4642            execute_insert(
4643                pool,
4644                gql_ctx,
4645                schema_name,
4646                table_name,
4647                objects,
4648                &context,
4649                mutation_type == MutationType::InsertOne,
4650            )
4651            .await?
4652        }
4653        // `UpdateMany` answered above: it is a list of responses rather than
4654        // one, so it does not share this shape.
4655        MutationType::UpdateMany | MutationType::Update | MutationType::UpdateByPk => {
4656            // `_set` replaces, the others read the column they write. A client
4657            // may send more than one, so all of them are collected rather than
4658            // the first that happens to be present.
4659            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4660                .iter()
4661                .filter_map(|name| {
4662                    ctx.args
4663                        .try_get(name)
4664                        .ok()
4665                        .map(|v| (*name, accessor_to_json(&v)))
4666                })
4667                .filter(|(_, value)| !value.is_null())
4668                .collect();
4669
4670            let where_clause = if mutation_type == MutationType::UpdateByPk {
4671                Some(pk_where_from_args(
4672                    ctx,
4673                    schema_name,
4674                    table_name,
4675                    pk_columns,
4676                    names.as_ref(),
4677                )?)
4678            } else {
4679                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4680            };
4681            // Which rows this role may change, beside which rows the request
4682            // asked to change. A by-key update is narrowed too: naming the key
4683            // of a row the permission withholds must not be a way to reach it.
4684            let where_clause = and_predicates(
4685                where_clause,
4686                permission_filter(
4687                    &gql_ctx.caller(),
4688                    names.as_ref(),
4689                    schema_name,
4690                    table_name,
4691                    crate::role::Verb::Update,
4692                )?,
4693            );
4694
4695            execute_update(
4696                pool,
4697                gql_ctx,
4698                schema_name,
4699                table_name,
4700                &type_name,
4701                operators,
4702                column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await,
4703                where_clause,
4704                relationships.as_ref(),
4705                names.as_ref(),
4706            )
4707            .await
4708            .map(|rows| {
4709                let count = rows.len();
4710                (rows, count)
4711            })?
4712        }
4713        MutationType::Delete | MutationType::DeleteByPk => {
4714            let where_clause = if mutation_type == MutationType::DeleteByPk {
4715                Some(pk_where_from_args(
4716                    ctx,
4717                    schema_name,
4718                    table_name,
4719                    pk_columns,
4720                    names.as_ref(),
4721                )?)
4722            } else {
4723                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4724            };
4725            let where_clause = and_predicates(
4726                where_clause,
4727                permission_filter(
4728                    &gql_ctx.caller(),
4729                    names.as_ref(),
4730                    schema_name,
4731                    table_name,
4732                    crate::role::Verb::Delete,
4733                )?,
4734            );
4735
4736            execute_delete(
4737                pool,
4738                gql_ctx,
4739                schema_name,
4740                table_name,
4741                &type_name,
4742                where_clause,
4743                returning,
4744                relationships.as_ref(),
4745                names.as_ref(),
4746                max_rows,
4747            )
4748            .await
4749            .map(|rows| {
4750                let count = rows.len();
4751                (rows, count)
4752            })?
4753        }
4754    };
4755
4756    let is_delete = matches!(
4757        mutation_type,
4758        MutationType::Delete | MutationType::DeleteByPk
4759    );
4760    let result = match returning {
4761        Some(returning) if !result.is_empty() && !is_delete => {
4762            reread_returning(
4763                pool,
4764                gql_ctx,
4765                schema_name,
4766                table_name,
4767                &type_name,
4768                result,
4769                returning,
4770                relationships.as_ref(),
4771                names.as_ref(),
4772                max_rows,
4773            )
4774            .await?
4775        }
4776        _ => result,
4777    };
4778
4779    Ok(mutation_result(result, affected, by_key))
4780}
4781
4782/// The transaction this operation's writes share, opening it if this is the
4783/// first one.
4784///
4785/// Held for the length of the write, and settled by whoever answers the
4786/// request -- see [`crate::context::SharedWrite`]. Mutation root fields are
4787/// resolved one after another, so the lock is never contended; it is what
4788/// makes the transaction reachable from a resolver that owns nothing.
4789async fn write_tx<'a>(
4790    gql_ctx: &'a GraphQLContext,
4791    pool: &PgPool,
4792) -> Result<
4793    tokio::sync::MutexGuard<'a, Option<sqlx::Transaction<'static, sqlx::Postgres>>>,
4794    async_graphql::Error,
4795> {
4796    let mut guard = gql_ctx.write.lock().await;
4797    if guard.is_none() {
4798        *guard = Some(begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?);
4799    }
4800    Ok(guard)
4801}
4802
4803/// Begin a transaction with the request's role and session variables applied.
4804///
4805/// The settings go in before the role does. A role with fewer privileges may
4806/// not be allowed to call `set_config` at all, and a policy that reads a
4807/// setting the caller could then change is not a policy.
4808async fn begin_with_session(
4809    pool: &PgPool,
4810    role: &str,
4811    settings: &[(String, String)],
4812) -> Result<sqlx::Transaction<'static, sqlx::Postgres>, async_graphql::Error> {
4813    let mut tx = pool.begin().await?;
4814
4815    // One statement for all of them, which is what the REST path does. A loop
4816    // here costs a round trip per session variable, and there is always at
4817    // least one: `hasura.session` and `hasura.role` are set on every request,
4818    // so even an anonymous read paid two. An authenticated caller with six
4819    // `x-hasura-*` variables paid eight.
4820    //
4821    // The values stay bound rather than interpolated. `set_config` takes both
4822    // as arguments, where `SET LOCAL` would need the value written into the
4823    // statement text.
4824    if !settings.is_empty() {
4825        let calls = (0..settings.len())
4826            .map(|i| format!("set_config(${}, ${}, true)", i * 2 + 1, i * 2 + 2))
4827            .collect::<Vec<_>>()
4828            .join(", ");
4829        let sql = format!("SELECT {calls}");
4830        let mut query = sqlx::query_scalar::<_, String>(&sql);
4831        for (name, value) in settings {
4832            query = query.bind(name).bind(value);
4833        }
4834        query.fetch_optional(&mut *tx).await?;
4835    }
4836
4837    sqlx::query(&format!(
4838        "SET LOCAL ROLE {}",
4839        postrust_sql::escape_ident(role)
4840    ))
4841    .execute(&mut *tx)
4842    .await?;
4843    Ok(tx)
4844}
4845
4846/// Render a row as JSON, with any shape column rendered as GeoJSON.
4847///
4848/// PostgreSQL's text form for a geometry is WKB hex --
4849/// `0103000020E6100000…` -- which is what `row_to_json` puts in the response
4850/// and is not what any client can read. Hasura answers GeoJSON, and a client
4851/// migrating from it parses GeoJSON.
4852///
4853/// The shape columns are merged over the row rather than replacing the
4854/// projection, so a table with no shape column produces exactly the SQL it did
4855/// before and one with three needs no list of the others.
4856fn row_json(expr: &str, column_types: &HashMap<String, String>) -> String {
4857    let mut shapes: Vec<(&String, &String)> = column_types
4858        .iter()
4859        .filter(|(_, pg_type)| matches!(pg_type.as_str(), "geometry" | "geography"))
4860        .collect();
4861    if shapes.is_empty() {
4862        return format!("row_to_json({})", expr);
4863    }
4864    shapes.sort();
4865
4866    let overrides: Vec<String> = shapes
4867        .iter()
4868        .map(|(name, _)| {
4869            format!(
4870                // Option 4 asks PostGIS for the long-form CRS member --
4871                // `urn:ogc:def:crs:EPSG::4326` -- which Hasura includes and
4872                // which a client round-tripping a shape needs in order to
4873                // write it back where it came from. PostGIS emits it only
4874                // where the geometry has an SRID to report.
4875                "{}, ST_AsGeoJSON({}.{}, 9, 4)::jsonb",
4876                postrust_sql::quote_literal(name),
4877                expr,
4878                postrust_sql::escape_ident(name)
4879            )
4880        })
4881        .collect();
4882    format!(
4883        "(to_jsonb({}) || jsonb_build_object({}))",
4884        expr,
4885        overrides.join(", ")
4886    )
4887}
4888
4889/// Execute a SQL query and return results as serde_json::Value.
4890/// We keep data as serde_json::Value so field resolvers can use try_downcast_ref.
4891async fn execute_query_on(
4892    conn: &mut sqlx::PgConnection,
4893    sql: &str,
4894    params: &[serde_json::Value],
4895) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4896    use sqlx::Row;
4897
4898    trace!("Executing SQL: {}", sql);
4899
4900    // Execute query
4901    let mut query = sqlx::query(sql);
4902    for param in params {
4903        query = bind_json_value(query, param);
4904    }
4905    let rows = query.fetch_all(&mut *conn).await.map_err(database_error)?;
4906
4907    // Return raw JSON values - don't convert to async_graphql::Value
4908    // This allows field resolvers to use try_downcast_ref::<serde_json::Value>()
4909    let results: Vec<serde_json::Value> = rows
4910        .into_iter()
4911        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
4912        .collect();
4913
4914    Ok(results)
4915}
4916
4917/// Refuse a GeoJSON document PostGIS would accept and should not.
4918///
4919/// `ST_GeomFromGeoJSON` will build a "Polygon" from three points that do not
4920/// meet, and a "LineString" from one point -- neither of which is a shape, and
4921/// both of which are then stored and read back as though they were. Hasura
4922/// checks the document before it reaches the database, refuses with
4923/// `parse-failed`, and says which rule was broken; the messages here are its
4924/// messages, because a client that reports them to a user is reporting text it
4925/// already ships.
4926///
4927/// A value that is not an object is left alone: a string in a geometry column
4928/// is WKT or WKB hex, which is a different thing entirely.
4929fn check_geojson(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4930    fn refuse<T>(message: &str) -> Result<T, async_graphql::Error> {
4931        Err(coded_error("parse-failed", message))
4932    }
4933
4934    fn position(
4935        value: &serde_json::Value,
4936    ) -> Result<&Vec<serde_json::Value>, async_graphql::Error> {
4937        match value.as_array() {
4938            Some(items) if items.len() >= 2 && items.iter().all(|i| i.is_number()) => Ok(items),
4939            _ => refuse("A Position needs at least 2 elements"),
4940        }
4941    }
4942
4943    fn positions(
4944        value: Option<&serde_json::Value>,
4945        least: usize,
4946        what: &str,
4947    ) -> Result<Vec<Vec<serde_json::Value>>, async_graphql::Error> {
4948        let items = match value.and_then(|v| v.as_array()) {
4949            Some(items) => items,
4950            None => return refuse(&format!("A {} needs at least {} Positions", what, least)),
4951        };
4952        if items.len() < least {
4953            return refuse(&format!("A {} needs at least {} Positions", what, least));
4954        }
4955        items.iter().map(|item| position(item).cloned()).collect()
4956    }
4957
4958    fn ring(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4959        let points = positions(Some(value), 4, "LinearRing")?;
4960        match (points.first(), points.last()) {
4961            (Some(first), Some(last)) if first == last => Ok(()),
4962            _ => refuse("the first and last locations have to be equal for a LinearRing"),
4963        }
4964    }
4965
4966    fn polygon(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4967        let Some(rings) = value.as_array() else {
4968            return refuse("A LinearRing needs at least 4 Positions");
4969        };
4970        rings.iter().try_for_each(ring)
4971    }
4972
4973    let serde_json::Value::Object(map) = value else {
4974        return Ok(());
4975    };
4976    let Some(kind) = map.get("type").and_then(|v| v.as_str()) else {
4977        return Ok(());
4978    };
4979    let coordinates = map.get("coordinates");
4980    match kind {
4981        "Point" => {
4982            position(coordinates.unwrap_or(&serde_json::Value::Null))?;
4983        }
4984        "MultiPoint" => {
4985            positions(coordinates, 0, "MultiPoint")?;
4986        }
4987        "LineString" => {
4988            positions(coordinates, 2, "LineString")?;
4989        }
4990        "MultiLineString" => {
4991            for line in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4992                positions(Some(line), 2, "LineString")?;
4993            }
4994        }
4995        "Polygon" => polygon(coordinates.unwrap_or(&serde_json::Value::Null))?,
4996        "MultiPolygon" => {
4997            for one in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4998                polygon(one)?;
4999            }
5000        }
5001        "GeometryCollection" => {
5002            for geometry in map
5003                .get("geometries")
5004                .and_then(|v| v.as_array())
5005                .into_iter()
5006                .flatten()
5007            {
5008                check_geojson(geometry)?;
5009            }
5010        }
5011        other => return refuse(&format!("unexpected geometry type: {}", other)),
5012    }
5013    Ok(())
5014}
5015
5016/// The call that produces a computed field's value.
5017///
5018/// Two spellings of the same row, because the two calls need different ones.
5019/// A positional call takes `alias.*`, which is what a qualified table can be
5020/// written as; a named call takes the row as one value, which only a bare
5021/// alias is. Named notation is needed exactly where a session argument is,
5022/// since the row is then not the only parameter and may not be the first.
5023fn computed_call(
5024    definition: &postrust_core::schema_cache::ComputedColumn,
5025    row: &str,
5026    row_by_name: &str,
5027) -> String {
5028    let function = format!(
5029        "{}.{}",
5030        postrust_sql::escape_ident(&definition.function.schema),
5031        postrust_sql::escape_ident(&definition.function.name)
5032    );
5033    match (&definition.session_argument, &definition.row_argument) {
5034        (Some(session), Some(row_argument)) => format!(
5035            "{}({} => {}, {} => {})",
5036            function,
5037            postrust_sql::escape_ident(row_argument),
5038            row_by_name,
5039            postrust_sql::escape_ident(session),
5040            SESSION_ARGUMENT
5041        ),
5042        _ => format!("{}({})", function, row),
5043    }
5044}
5045
5046/// What a `hasura_session` argument is given.
5047///
5048/// The session document, read from the setting `begin_with_session` writes.
5049/// `current_setting`'s second argument makes a missing setting null rather than
5050/// an error, which is the case of a request with no session at all.
5051const SESSION_ARGUMENT: &str = "coalesce(current_setting('hasura.session', true), '{}')::json";
5052
5053/// Everything an update may be told to do, in the order the schema declares
5054/// them. Read by both spellings: one update, and one of many.
5055const UPDATE_OPERATORS: [&str; 7] = [
5056    "_set",
5057    "_inc",
5058    "_append",
5059    "_prepend",
5060    "_delete_key",
5061    "_delete_elem",
5062    "_delete_at_path",
5063];
5064
5065/// What a read table is called inside its own query.
5066///
5067/// See [`WRITTEN_ROW`]: the row a query passes to a computed field has to be
5068/// named, and a table's qualified name is not a name a row can be written
5069/// under.
5070const READ_ROW: &str = "pgrst_src";
5071
5072/// What a written table is called inside its own statement.
5073///
5074/// See [`WhereScope::under_alias`]: the row a statement returns has to be
5075/// named, and a table's own name is read as a column first.
5076const WRITTEN_ROW: &str = "pgrst_row";
5077
5078/// The cast a written value needs to reach a column of this type.
5079///
5080/// A bound parameter arrives as text. PostgreSQL will coerce it to a numeric
5081/// or a date on assignment, but not to `jsonb`, an array, or a user-defined
5082/// type -- `column "details" is of type jsonb but expression is of type text`
5083/// is what an uncast insert answers. Naming the column's own type covers all
5084/// of them and changes nothing where the coercion would have worked anyway.
5085fn write_cast(column_types: &HashMap<String, String>, column: &str) -> String {
5086    match column_types.get(column) {
5087        Some(pg_type) => format!("::{}", pg_type),
5088        None => String::new(),
5089    }
5090}
5091
5092/// The value one column is written with, as it goes over the wire.
5093///
5094/// A `json` column holds a JSON value, and `"[]"` is one -- a string whose
5095/// characters happen to be brackets. Binding the bare text and casting stored
5096/// an empty array instead, so a client that wrote a string read back a list.
5097/// The document a column is given is the document it keeps.
5098fn write_operand(
5099    column_types: &HashMap<String, String>,
5100    column: &str,
5101    value: &serde_json::Value,
5102) -> serde_json::Value {
5103    match column_types.get(column).map(String::as_str) {
5104        Some("json") | Some("jsonb") => match value {
5105            serde_json::Value::String(_)
5106            | serde_json::Value::Number(_)
5107            | serde_json::Value::Bool(_) => serde_json::Value::String(value.to_string()),
5108            other => other.clone(),
5109        },
5110        // An array column takes an array literal. `["a","b"]` is JSON, and
5111        // PostgreSQL reads a text parameter destined for a `text[]` as
5112        // `{a,b}` -- `malformed array literal` is what it says about the
5113        // other spelling.
5114        Some(pg_type) if is_array_type(pg_type) => match value {
5115            serde_json::Value::Array(_) => serde_json::Value::String(array_literal(value)),
5116            other => other.clone(),
5117        },
5118        _ => value.clone(),
5119    }
5120}
5121
5122/// Whether a PostgreSQL type name is an array.
5123///
5124/// Both spellings appear in the catalogue depending on how it was asked:
5125/// `text[]` from `format_type`, `_text` from `typname`.
5126fn is_array_type(pg_type: &str) -> bool {
5127    pg_type.ends_with("[]") || pg_type.starts_with('_')
5128}
5129
5130/// A JSON array as PostgreSQL writes one.
5131///
5132/// `["a","b"]` becomes `{"a","b"}`. Every element is quoted, so a value
5133/// containing a comma, a brace or a backslash survives; a null is the unquoted
5134/// `NULL`, which is the only way to write one.
5135fn array_literal(value: &serde_json::Value) -> String {
5136    fn element(value: &serde_json::Value) -> String {
5137        match value {
5138            serde_json::Value::Null => "NULL".to_string(),
5139            serde_json::Value::Array(_) => array_literal(value),
5140            serde_json::Value::String(text) => {
5141                format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
5142            }
5143            other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
5144        }
5145    }
5146    let items = match value {
5147        serde_json::Value::Array(items) => items,
5148        other => return element(other),
5149    };
5150    format!(
5151        "{{{}}}",
5152        items.iter().map(element).collect::<Vec<_>>().join(",")
5153    )
5154}
5155
5156/// The expression that writes one bound value into one column.
5157///
5158/// A cast is enough for almost everything. A shape is the exception: a client
5159/// sends GeoJSON, which is what Hasura accepts, and `'{"type":"Point",…}'` is
5160/// not something PostgreSQL will cast to a geometry -- it has a function for
5161/// reading that, and the function is the only way in.
5162///
5163/// Only when the value actually is an object. A string in a geometry column is
5164/// WKT or WKB hex, which the cast does read, and passing that to
5165/// `ST_GeomFromGeoJSON` would refuse a perfectly good value.
5166fn write_expression(
5167    column_types: &HashMap<String, String>,
5168    column: &str,
5169    value: &serde_json::Value,
5170    placeholder: &str,
5171) -> String {
5172    let pg_type = column_types.get(column).map(String::as_str);
5173    if value.is_object() && matches!(pg_type, Some("geometry") | Some("geography")) {
5174        return match pg_type {
5175            Some("geography") => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
5176            _ => format!("ST_GeomFromGeoJSON({})", placeholder),
5177        };
5178    }
5179    // An amount written as a number is reached through one: PostgreSQL has no
5180    // cast from `double precision` to `money`, which is what a bound JSON
5181    // number arrives as, and `numeric` is the type it does take one from. One
5182    // written as text -- `"$12,344.57"` -- is read by `money` directly, and
5183    // going through `numeric` would refuse the currency symbol.
5184    if pg_type == Some("money") && value.is_number() {
5185        return format!("{}::numeric::money", placeholder);
5186    }
5187    format!("{}{}", placeholder, write_cast(column_types, column))
5188}
5189
5190/// Every column of a table with the type it is declared as.
5191async fn column_types_of(
5192    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
5193    schema_name: &str,
5194    table_name: &str,
5195) -> HashMap<String, String> {
5196    let Ok(guard) = schema_cache.get().await else {
5197        return HashMap::new();
5198    };
5199    let Some(cache) = guard.as_ref() else {
5200        return HashMap::new();
5201    };
5202    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5203    match cache.get_table(&qi) {
5204        Some(table) => table
5205            .columns
5206            .values()
5207            .map(|c| (c.name.clone(), c.nominal_type.clone()))
5208            .collect(),
5209        None => HashMap::new(),
5210    }
5211}
5212
5213/// Insert one row, and any rows nested inside it.
5214///
5215/// Hasura writes a parent and its children in one mutation:
5216///
5217/// ```graphql
5218/// insert_article(objects: [{title: "x", author: {data: {name: "y"}}}])
5219/// ```
5220///
5221/// Which row goes first follows from which side holds the key. A relationship
5222/// to one row is reached through a column of *this* table, so the related row
5223/// is inserted first and its key is what this row's column is set to. A
5224/// relationship to many rows is reached through a column of *theirs*, so this
5225/// row goes first and each child's column is set from it.
5226///
5227/// Everything runs in the transaction the caller opened, so a child that
5228/// cannot be written takes the parent with it -- which is what writing them in
5229/// one mutation means.
5230fn insert_row<'life>(
5231    conn: &'life mut sqlx::PgConnection,
5232    schema_name: &'life str,
5233    table_name: &'life str,
5234    object: serde_json::Map<String, serde_json::Value>,
5235    context: &'life InsertContext<'life>,
5236    written_count: &'life mut usize,
5237) -> std::pin::Pin<
5238    Box<
5239        dyn std::future::Future<Output = Result<serde_json::Value, async_graphql::Error>>
5240            + Send
5241            + 'life,
5242    >,
5243> {
5244    Box::pin(async move {
5245        use sqlx::Row;
5246
5247        let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5248        let table = context.cache.get_table(&qi).ok_or_else(|| {
5249            async_graphql::Error::new(format!("unknown table \"{}\"", table_name))
5250        })?;
5251        let column_types: HashMap<String, String> = table
5252            .columns
5253            .values()
5254            .map(|c| (c.name.clone(), c.nominal_type.clone()))
5255            .collect();
5256
5257        // A key that is a relationship rather than a column is a nested write.
5258        let type_name = context
5259            .type_names
5260            .get(&(schema_name.to_string(), table_name.to_string()));
5261        let relationships: &[RelationshipField] = type_name
5262            .and_then(|name| context.relationships.get(name))
5263            .map(|r| r.as_slice())
5264            .unwrap_or(&[]);
5265
5266        let mut columns = serde_json::Map::new();
5267        type Nested<'r> = (
5268            &'r RelationshipField,
5269            serde_json::Value,
5270            Option<serde_json::Value>,
5271        );
5272        let mut to_one: Vec<Nested> = Vec::new();
5273        let mut to_many: Vec<Nested> = Vec::new();
5274
5275        for (key, value) in object {
5276            match relationships.iter().find(|r| r.name == key) {
5277                None => {
5278                    // A written value is keyed by the field it was sent under;
5279                    // the statement is written in the table's own columns.
5280                    columns.insert(
5281                        table_column_for(context.names, table, &key).to_string(),
5282                        value,
5283                    );
5284                }
5285                Some(_) if value.is_null() => {
5286                    // `author: null` is a row with no author, not a row whose
5287                    // author is a null object. A client writing one insert for
5288                    // both cases sends the relationship either way.
5289                    continue;
5290                }
5291                Some(relationship) => {
5292                    // `{data: {...}}` for one row, `{data: [{...}]}` for many,
5293                    // and an `on_conflict` beside either -- a nested row is
5294                    // upserted the same way a top-level one is.
5295                    let (data, conflict) = match &value {
5296                        serde_json::Value::Object(map) => (
5297                            map.get("data").cloned().unwrap_or(value.clone()),
5298                            map.get("on_conflict").cloned().filter(|v| !v.is_null()),
5299                        ),
5300                        other => (other.clone(), None),
5301                    };
5302                    // Which row goes first follows from which side holds the
5303                    // key, not from how many rows there are. A one-to-one
5304                    // whose child key *is* the parent's -- `author_detail.id`
5305                    // referencing `author.id` -- is one row either way, and
5306                    // the parent still has to be written before there is a key
5307                    // to give it.
5308                    if child_holds_the_key(&relationship.relationship) {
5309                        to_many.push((relationship, data, conflict));
5310                    } else {
5311                        to_one.push((relationship, data, conflict));
5312                    }
5313                }
5314            }
5315        }
5316
5317        // A shape is checked before it is written, since PostGIS would build
5318        // one from a document that is not a shape.
5319        for (column, value) in &columns {
5320            if matches!(
5321                column_types.get(column).map(String::as_str),
5322                Some("geometry") | Some("geography")
5323            ) {
5324                // Under the name the client wrote it under, which is the
5325                // exposed one rather than the column's where they differ.
5326                check_geojson(value).map_err(|error| {
5327                    let field = context
5328                        .names
5329                        .column(schema_name, table_name, column)
5330                        .unwrap_or(column);
5331                    at_path(error, &format!("{}.{}", context.row, field))
5332                })?;
5333            }
5334        }
5335
5336        // The rows this one points at, first: this row's own column carries
5337        // their key.
5338        for (relationship, data, conflict) in to_one {
5339            let plan =
5340                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5341                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5342            let serde_json::Value::Object(child) = data else {
5343                return Err(async_graphql::Error::new(format!(
5344                    "\"{}\" takes an object to insert",
5345                    relationship.name
5346                )));
5347            };
5348            // Writing the related row is what fills this row's key column in,
5349            // so a value already written there is a second answer to the same
5350            // question and one of them would be silently discarded.
5351            for (local, _) in &plan.columns {
5352                if columns.get(local).is_some_and(|v| !v.is_null()) {
5353                    return Err(at_path(
5354                        validation_error(format!(
5355                            "cannot insert object relationship \"{}\" as \"{}\" column \
5356                             values are already determined",
5357                            relationship.name, local
5358                        )),
5359                        &format!("{}.{}", context.row, relationship.name),
5360                    ));
5361                }
5362            }
5363            // A to-one row is written under the relationship's own name:
5364            // `...objects[0].author.data`, and its `on_conflict` beside it.
5365            let under = format!("{}.{}", context.row, relationship.name);
5366            let nested = InsertContext {
5367                on_conflict: conflict,
5368                cache: context.cache,
5369                relationships: context.relationships,
5370                type_names: context.type_names,
5371                names: context.names,
5372                caller: context.caller,
5373                row: format!("{}.data", under),
5374                objects: format!("{}.data", under),
5375                conflict: format!("{}.on_conflict", under),
5376            };
5377            let written = insert_row(
5378                conn,
5379                &plan.foreign_schema,
5380                &plan.foreign_table,
5381                child,
5382                &nested,
5383                written_count,
5384            )
5385            .await?;
5386            for (local, foreign) in &plan.columns {
5387                if let Some(value) = written.get(foreign) {
5388                    columns.insert(local.clone(), value.clone());
5389                }
5390            }
5391        }
5392
5393        // What the server fills in regardless of what was sent. Applied after
5394        // the nested rows have contributed their keys and before the columns
5395        // are counted, because a preset is a column this statement writes --
5396        // and applied *over* the request, since a preset the client could
5397        // override is not a preset.
5398        for (column, value) in crate::role::presets(
5399            &context.caller,
5400            context.names,
5401            schema_name,
5402            table_name,
5403            crate::role::Verb::Insert,
5404        )
5405        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?
5406        {
5407            columns.insert(column, value);
5408        }
5409
5410        // `ON CONFLICT` says which uniqueness is being resolved against and
5411        // what to do about it. An empty `update_columns` is `DO NOTHING`,
5412        // which is how a client says "leave the row that is already there".
5413        // The columns this statement writes, settled before the conflict
5414        // clause so a predicate on it can number its parameters after theirs.
5415        let column_names: Vec<&str> = columns.keys().map(|k| k.as_str()).collect();
5416        let mut conflict_values: Vec<serde_json::Value> = Vec::new();
5417        let conflict_sql = match &context.on_conflict {
5418            Some(serde_json::Value::Object(spec)) => {
5419                let constraint = spec.get("constraint").and_then(|v| v.as_str());
5420                let Some(constraint) = constraint else {
5421                    return Err(async_graphql::Error::new(
5422                        "on_conflict needs a constraint to resolve against",
5423                    ));
5424                };
5425                if !table
5426                    .unique_constraints
5427                    .iter()
5428                    .any(|(name, _)| name == constraint)
5429                {
5430                    return Err(async_graphql::Error::new(format!(
5431                        "\"{}\" is not a unique constraint of \"{}\"",
5432                        constraint, table_name
5433                    )));
5434                }
5435                let updates: Vec<&str> = spec
5436                    .get("update_columns")
5437                    .and_then(|v| v.as_array())
5438                    .map(|items| items.iter().filter_map(|i| i.as_str()).collect())
5439                    .unwrap_or_default();
5440                // The member that stands for the columns this role has none
5441                // of. It is in the enum so that `on_conflict` exists at all;
5442                // naming it is the one thing it cannot be used for.
5443                if updates.contains(&PLACEHOLDER_COLUMN) {
5444                    return Err(at_path(
5445                        coded_error("validation-failed", "erroneous column name"),
5446                        &context.conflict,
5447                    ));
5448                }
5449
5450                if updates.is_empty() {
5451                    format!(
5452                        " ON CONFLICT ON CONSTRAINT {} DO NOTHING",
5453                        postrust_sql::escape_ident(constraint)
5454                    )
5455                } else {
5456                    // An `ON CONFLICT DO UPDATE` is an update, so the update
5457                    // permission's presets are written here too -- and a
5458                    // column a preset writes does not also take its value from
5459                    // `EXCLUDED`, which is what "a preset overrides the
5460                    // request" means on this side.
5461                    let preset = crate::role::presets(
5462                        &context.caller,
5463                        context.names,
5464                        schema_name,
5465                        table_name,
5466                        crate::role::Verb::Update,
5467                    )
5468                    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5469
5470                    // Numbered after the row's own values and before the
5471                    // predicate below, because `SET` is written before `WHERE`
5472                    // and the parameters are bound in the order they appear.
5473                    let mut param_idx = column_names.len() + 1;
5474                    let mut assignments: Vec<String> =
5475                        Vec::with_capacity(preset.len() + updates.len());
5476                    for (column, value) in &preset {
5477                        assignments.push(format!(
5478                            "{} = {}",
5479                            postrust_sql::escape_ident(column),
5480                            write_expression(
5481                                &column_types,
5482                                column,
5483                                value,
5484                                &format!("${}", param_idx)
5485                            )
5486                        ));
5487                        conflict_values.push(write_operand(&column_types, column, value));
5488                        param_idx += 1;
5489                    }
5490                    for field in &updates {
5491                        let column = table_column_for(context.names, table, field);
5492                        if preset.iter().any(|(written, _)| written == column) {
5493                            continue;
5494                        }
5495                        assignments.push(format!(
5496                            "{} = EXCLUDED.{}",
5497                            postrust_sql::escape_ident(column),
5498                            postrust_sql::escape_ident(column)
5499                        ));
5500                    }
5501                    // `where` on an upsert decides whether the row that is
5502                    // already there is overwritten -- "only if what I am
5503                    // writing is newer". It reads the existing row, which is
5504                    // what the statement's alias names.
5505                    //
5506                    // The update permission's own filter is ANDed into it: an
5507                    // upsert that overwrites a row is an update, and a role
5508                    // that may not update a row may not reach it by inserting
5509                    // over it. Compiling that filter is also where a session
5510                    // variable it names and the caller does not carry is
5511                    // noticed -- which is the answer Hasura gives before it
5512                    // writes anything.
5513                    let granted = permission_filter(
5514                        &context.caller,
5515                        context.names,
5516                        schema_name,
5517                        table_name,
5518                        crate::role::Verb::Update,
5519                    )?;
5520                    let requested = spec.get("where").filter(|f| !f.is_null()).cloned();
5521                    let condition = match and_predicates(requested, granted) {
5522                        Some(filter) => {
5523                            let filter = &filter;
5524                            let type_name = context
5525                                .type_names
5526                                .get(&(schema_name.to_string(), table_name.to_string()))
5527                                .map(String::as_str)
5528                                .unwrap_or(table_name);
5529                            let scope = WhereScope::table(
5530                                schema_name,
5531                                table_name,
5532                                type_name,
5533                                context.names,
5534                            )
5535                            .under_alias(WRITTEN_ROW)
5536                            .with_resolution(context.cache, context.relationships);
5537                            let mut alias_counter = 0usize;
5538                            build_condition(
5539                                filter,
5540                                &scope,
5541                                &mut param_idx,
5542                                &mut conflict_values,
5543                                &mut alias_counter,
5544                            )?
5545                            .map(|sql| format!(" WHERE {}", sql))
5546                            .unwrap_or_default()
5547                        }
5548                        None => String::new(),
5549                    };
5550                    format!(
5551                        " ON CONFLICT ON CONSTRAINT {} DO UPDATE SET {}{}",
5552                        postrust_sql::escape_ident(constraint),
5553                        assignments.join(", "),
5554                        condition
5555                    )
5556                }
5557            }
5558            _ => String::new(),
5559        };
5560
5561        // The row this statement writes, as `RETURNING` names it. Not the
5562        // table's own name: qualifying it reads as a column of a table called
5563        // `public`, which is what `missing FROM-clause entry for table
5564        // "public"` means, and leaving it bare reads as a column of the table
5565        // where one shares the name.
5566        let qualified_table = postrust_sql::escape_ident(WRITTEN_ROW);
5567
5568        // What the written row has to satisfy for the write to stand,
5569        // evaluated in `RETURNING` against the row as written. Its parameters
5570        // are numbered after the values and the conflict clause, since
5571        // `RETURNING` is last.
5572        let check_scope = WhereScope::for_alias(
5573            schema_name,
5574            table_name,
5575            WRITTEN_ROW,
5576            context
5577                .type_names
5578                .get(&(schema_name.to_string(), table_name.to_string()))
5579                .map(String::as_str)
5580                .unwrap_or(table_name),
5581            context.cache,
5582            context.relationships,
5583            context.names,
5584        );
5585        let (check_sql, check_values) = {
5586            let check = crate::role::write_check(
5587                &context.caller,
5588                context.names,
5589                schema_name,
5590                table_name,
5591                crate::role::Verb::Insert,
5592            )
5593            .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5594            match check {
5595                None => (String::new(), Vec::new()),
5596                Some(check) => {
5597                    let mut values = Vec::new();
5598                    let mut param_idx = column_names.len() + conflict_values.len() + 1;
5599                    let mut alias_counter = 0usize;
5600                    match build_condition(
5601                        &check,
5602                        &check_scope,
5603                        &mut param_idx,
5604                        &mut values,
5605                        &mut alias_counter,
5606                    )? {
5607                        None => (String::new(), Vec::new()),
5608                        Some(sql) => (
5609                            format!(
5610                                ", ({}) AS {}",
5611                                sql,
5612                                postrust_sql::escape_ident(CHECK_COLUMN)
5613                            ),
5614                            values,
5615                        ),
5616                    }
5617                }
5618            }
5619        };
5620
5621        let names = column_names;
5622        let written = if names.is_empty() {
5623            // Every column defaulted. `DEFAULT VALUES` is how SQL says that;
5624            // an empty column list is a syntax error.
5625            let sql = format!(
5626                "INSERT INTO {}.{} AS {} DEFAULT VALUES{} RETURNING {}{}",
5627                postrust_sql::escape_ident(schema_name),
5628                postrust_sql::escape_ident(table_name),
5629                qualified_table,
5630                conflict_sql,
5631                row_json(&qualified_table, &column_types),
5632                check_sql
5633            );
5634            let mut query = sqlx::query(&sql);
5635            for value in &conflict_values {
5636                query = bind_json_value(query, value);
5637            }
5638            for value in &check_values {
5639                query = bind_json_value(query, value);
5640            }
5641            query
5642                .fetch_optional(&mut *conn)
5643                .await
5644                // A constraint the write violated is reported against the rows
5645                // the statement was given, which is where Hasura reports it.
5646                .map_err(|error| at_path(database_error(error), &context.objects))?
5647        } else {
5648            let placeholders: Vec<String> = names
5649                .iter()
5650                .enumerate()
5651                .map(|(i, column)| {
5652                    let placeholder = format!("${}", i + 1);
5653                    match columns.get(*column) {
5654                        Some(value) => write_expression(&column_types, column, value, &placeholder),
5655                        None => placeholder,
5656                    }
5657                })
5658                .collect();
5659            let sql = format!(
5660                "INSERT INTO {}.{} AS {} ({}) VALUES ({}){} RETURNING {}{}",
5661                postrust_sql::escape_ident(schema_name),
5662                postrust_sql::escape_ident(table_name),
5663                qualified_table,
5664                names
5665                    .iter()
5666                    .map(|c| postrust_sql::escape_ident(c))
5667                    .collect::<Vec<_>>()
5668                    .join(", "),
5669                placeholders.join(", "),
5670                conflict_sql,
5671                row_json(&qualified_table, &column_types),
5672                check_sql
5673            );
5674            trace!("Executing INSERT SQL: {}", sql);
5675            let mut query = sqlx::query(&sql);
5676            for column in &names {
5677                if let Some(value) = columns.get(*column) {
5678                    query = bind_json_value(query, &write_operand(&column_types, column, value));
5679                }
5680            }
5681            for value in &conflict_values {
5682                query = bind_json_value(query, value);
5683            }
5684            for value in &check_values {
5685                query = bind_json_value(query, value);
5686            }
5687            query
5688                .fetch_optional(&mut *conn)
5689                .await
5690                // A constraint the write violated is reported against the rows
5691                // the statement was given, which is where Hasura reports it.
5692                .map_err(|error| at_path(database_error(error), &context.objects))?
5693        };
5694
5695        // `DO NOTHING` writes nothing and returns nothing, which is the answer
5696        // rather than an error: the row that was already there stays.
5697        let Some(written) = written else {
5698            return Ok(serde_json::Value::Null);
5699        };
5700        refuse_unchecked_rows(std::slice::from_ref(&written), !check_sql.is_empty())
5701            .map_err(|error| at_path(error, &context.objects))?;
5702        let row: serde_json::Value = written.try_get(0).unwrap_or(serde_json::Value::Null);
5703        *written_count += 1;
5704
5705        // Then the rows that point at this one, which need its key.
5706        for (relationship, data, conflict) in to_many {
5707            let plan =
5708                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5709                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5710            let children = match data {
5711                serde_json::Value::Array(items) => items,
5712                serde_json::Value::Object(map) => vec![serde_json::Value::Object(map)],
5713                serde_json::Value::Null => Vec::new(),
5714                _ => {
5715                    return Err(async_graphql::Error::new(format!(
5716                        "\"{}\" takes objects to insert",
5717                        relationship.name
5718                    )))
5719                }
5720            };
5721            for (child_index, child) in children.into_iter().enumerate() {
5722                let serde_json::Value::Object(mut child) = child else {
5723                    continue;
5724                };
5725                for (local, foreign) in &plan.columns {
5726                    // The parent's key is what makes the child a child, so a
5727                    // child that writes it too is asking for a different
5728                    // parent than the one it is nested inside.
5729                    if child.get(foreign).is_some_and(|v| !v.is_null()) {
5730                        return Err(at_path(
5731                            validation_error(format!(
5732                                "cannot insert \"{}\" columns as their values are already \
5733                                 being determined by parent insert",
5734                                foreign
5735                            )),
5736                            &format!(
5737                                "{}.{}{}",
5738                                context.row,
5739                                relationship.name,
5740                                match relationship.is_list {
5741                                    true => format!(".data[{}]", child_index),
5742                                    false => ".data".to_string(),
5743                                }
5744                            ),
5745                        ));
5746                    }
5747                    if let Some(value) = row.get(local) {
5748                        child.insert(foreign.clone(), value.clone());
5749                    }
5750                }
5751                // `...objects[0].articles.data[0]`, which is the path
5752                // Hasura answers for a child that writes its parent's key.
5753                // One row written after its parent -- a mapped object
5754                // relationship with `after_parent` -- has no index: there is
5755                // no list for it to be an item of.
5756                let at = match relationship.is_list {
5757                    true => format!(".data[{}]", child_index),
5758                    false => ".data".to_string(),
5759                };
5760                let under = format!("{}.{}", context.row, relationship.name);
5761                let nested = InsertContext {
5762                    on_conflict: conflict.clone(),
5763                    cache: context.cache,
5764                    relationships: context.relationships,
5765                    type_names: context.type_names,
5766                    names: context.names,
5767                    caller: context.caller,
5768                    row: format!("{}{}", under, at),
5769                    objects: format!("{}.data", under),
5770                    conflict: format!("{}.on_conflict", under),
5771                };
5772                insert_row(
5773                    conn,
5774                    &plan.foreign_schema,
5775                    &plan.foreign_table,
5776                    child,
5777                    &nested,
5778                    written_count,
5779                )
5780                .await?;
5781            }
5782        }
5783
5784        Ok(row)
5785    })
5786}
5787
5788/// What a nested insert needs to follow a relationship.
5789///
5790/// Cloned per row and per nested row, which costs three strings: the paths are
5791/// the only part that differs between them, and the rest is references and a
5792/// `Caller`.
5793#[derive(Clone)]
5794struct InsertContext<'a> {
5795    /// What to do when this row conflicts. A nested row carries its own, from
5796    /// the `on_conflict` beside its `data`.
5797    on_conflict: Option<serde_json::Value>,
5798    cache: &'a SchemaCache,
5799    relationships: &'a HashMap<String, Vec<RelationshipField>>,
5800    /// (schema, table) -> the GraphQL type name, which keys the relationship
5801    /// map. A table's type is not always its name: a second schema prefixes
5802    /// it, and a name may have been given.
5803    type_names: &'a HashMap<(String, String), String>,
5804    /// The names columns are exposed under, so a written value keyed by a
5805    /// field reaches the column it names.
5806    names: &'a crate::names::NameOverrides,
5807    /// Who is writing, so that a permission's presets and its check reach
5808    /// every row this insert writes -- including the nested ones, which are
5809    /// rows of other tables under other rules.
5810    caller: crate::role::Caller<'a>,
5811    /// Where this row is in the request, as Hasura writes such a place:
5812    /// `$.selectionSet.insert_author.args.objects[0]`, and one level down
5813    /// `$.selectionSet.insert_author.args.objects[0].articles.data[0]`. A
5814    /// column of it is that with the field's name appended.
5815    row: String,
5816    /// Where the rows this statement writes were named. What a refused `check`
5817    /// is reported against -- the argument, not the row inside it, which is
5818    /// what Hasura answers.
5819    objects: String,
5820    /// Where this statement's `on_conflict` was named.
5821    conflict: String,
5822}
5823
5824/// Re-read written rows so that a mutation's `returning` can carry
5825/// relationships and computed fields.
5826///
5827/// `RETURNING row_to_json(t.*)` gives the table's own columns and nothing
5828/// else, so a relationship asked for beside them had no value to resolve --
5829/// and a non-null list field with no value is an error, which meant a mutation
5830/// that had written its rows correctly answered as though it had failed. The
5831/// write is not in doubt; only the shape of the answer is.
5832///
5833/// So the rows are read again by their primary key, through the same
5834/// projection an ordinary query uses. One extra round trip, and only when the
5835/// selection actually asks for something `RETURNING` cannot give.
5836#[allow(clippy::too_many_arguments)]
5837async fn reread_returning(
5838    pool: &PgPool,
5839    gql_ctx: &GraphQLContext,
5840    schema_name: &str,
5841    table_name: &str,
5842    type_name: &str,
5843    rows: Vec<Value>,
5844    returning: async_graphql::SelectionField<'_>,
5845    relationships: &HashMap<String, Vec<RelationshipField>>,
5846    names: &crate::names::NameOverrides,
5847    max_rows: Option<i64>,
5848) -> Result<Vec<Value>, async_graphql::Error> {
5849    let guard = gql_ctx
5850        .schema_cache
5851        .get()
5852        .await
5853        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5854    let Some(cache) = guard.as_ref() else {
5855        return Ok(rows);
5856    };
5857    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5858    let Some(table) = cache.get_table(&qi) else {
5859        return Ok(rows);
5860    };
5861    if table.pk_cols.is_empty() {
5862        // Nothing to identify the rows by. The columns are still right.
5863        return Ok(rows);
5864    }
5865
5866    let mut param_idx = 1usize;
5867    let mut values: Vec<serde_json::Value> = Vec::new();
5868    let mut alias_counter = 0usize;
5869    let embeds = build_embed_expressions(
5870        &gql_ctx.caller(),
5871        cache,
5872        relationships,
5873        type_name,
5874        "src",
5875        returning.selection_set(),
5876        max_rows,
5877        &mut alias_counter,
5878        &mut param_idx,
5879        &mut values,
5880        names,
5881    )?;
5882    let computed = computed_projections(
5883        table,
5884        returning.selection_set(),
5885        "src",
5886        names,
5887        cache,
5888        &mut param_idx,
5889        &mut values,
5890    )?;
5891    // A rename is reason enough to read the rows again: `RETURNING` answers in
5892    // the table's own column names, which are not the names the client asked
5893    // under.
5894    let renames = names.renames_columns(&table.schema, &table.name);
5895    if embeds.is_empty() && computed.is_empty() && !renames {
5896        return Ok(rows);
5897    }
5898
5899    // One row per key written, in the order they were written.
5900    let mut conditions = Vec::with_capacity(rows.len());
5901    for row in &rows {
5902        let mut parts = Vec::with_capacity(table.pk_cols.len());
5903        for column in &table.pk_cols {
5904            let Value::Object(map) = row else {
5905                return Ok(rows);
5906            };
5907            let Some(value) = map.get(&async_graphql::Name::new(column)) else {
5908                return Ok(rows);
5909            };
5910            parts.push(format!(
5911                "{} = ${}",
5912                postrust_sql::escape_ident(column),
5913                param_idx
5914            ));
5915            values.push(value_to_json(value));
5916            param_idx += 1;
5917        }
5918        conditions.push(format!("({})", parts.join(" AND ")));
5919    }
5920    if conditions.is_empty() {
5921        return Ok(rows);
5922    }
5923
5924    let mut projection =
5925        rename_projection(table, "src", names).unwrap_or_else(|| "src.*".to_string());
5926    for expression in &computed {
5927        projection.push_str(", ");
5928        projection.push_str(expression);
5929    }
5930    for (name, expression) in &embeds {
5931        projection.push_str(", ");
5932        projection.push_str(expression);
5933        projection.push_str(" AS ");
5934        projection.push_str(&postrust_sql::escape_ident(name));
5935    }
5936
5937    let sql = format!(
5938        "SELECT {} FROM (SELECT {} FROM (SELECT * FROM {}.{} WHERE {}) AS src) AS pgrst_r",
5939        row_json("pgrst_r", &exposed_column_types(table, names)),
5940        projection,
5941        postrust_sql::escape_ident(schema_name),
5942        postrust_sql::escape_ident(table_name),
5943        conditions.join(" OR ")
5944    );
5945
5946    // The same transaction the write went into: the rows it is reading are the
5947    // rows that write produced, and nothing has committed yet.
5948    let mut guard = write_tx(gql_ctx, pool).await?;
5949    let conn = guard.as_mut().expect("write_tx opens one");
5950    let reread = execute_query_on(conn, &sql, &values).await?;
5951    drop(guard);
5952
5953    if reread.is_empty() {
5954        return Ok(rows);
5955    }
5956    Ok(reread.into_iter().map(json_to_value).collect())
5957}
5958
5959/// Execute an insert mutation.
5960async fn execute_insert(
5961    pool: &PgPool,
5962    gql_ctx: &GraphQLContext,
5963    schema_name: &str,
5964    table_name: &str,
5965    objects: serde_json::Value,
5966    context: &InsertContext<'_>,
5967    // Whether the client wrote one row rather than a list of them. Only the
5968    // error paths care: `insert_x_one(object: {...})` has no row to index, so
5969    // its rows are named `args.object` and not `args.object[0]`.
5970    single: bool,
5971) -> Result<(Vec<Value>, usize), async_graphql::Error> {
5972    trace!("Insert mutation for {}: {:?}", table_name, objects);
5973
5974    // Handle both array and single object
5975    let objects_array = match objects {
5976        serde_json::Value::Array(arr) => arr,
5977        serde_json::Value::Object(obj) => vec![serde_json::Value::Object(obj)],
5978        _ => {
5979            return Err(async_graphql::Error::new(
5980                "objects must be an array or object",
5981            ))
5982        }
5983    };
5984
5985    if objects_array.is_empty() {
5986        return Err(async_graphql::Error::new("objects cannot be empty"));
5987    }
5988
5989    let mut guard = write_tx(gql_ctx, pool).await?;
5990    let conn = guard.as_mut().expect("write_tx opens one");
5991
5992    let mut inserted: Vec<Value> = Vec::new();
5993    let mut written = 0usize;
5994
5995    for (index, object) in objects_array.into_iter().enumerate() {
5996        let serde_json::Value::Object(map) = object else {
5997            return Err(async_graphql::Error::new(
5998                "each object to insert is an object",
5999            ));
6000        };
6001        // This row's own place in the request, which is what an error inside
6002        // it is reported against.
6003        let context = InsertContext {
6004            row: match single {
6005                true => context.objects.clone(),
6006                false => format!("{}[{}]", context.objects, index),
6007            },
6008            ..context.clone()
6009        };
6010        let row = insert_row(conn, schema_name, table_name, map, &context, &mut written).await?;
6011        // A row `DO NOTHING` left alone is not in `returning` and is not in
6012        // `affected_rows` either: nothing was written, and the row that was
6013        // already there is not this mutation's to report.
6014        if !row.is_null() {
6015            inserted.push(json_to_value(row));
6016        }
6017    }
6018
6019    Ok((inserted, written))
6020}
6021
6022/// Shape a mutation's result for the field that asked for it.
6023///
6024/// A by-key mutation answers with the row, or with null where the key matched
6025/// nothing. Every other mutation answers with the table's mutation response,
6026/// which carries `affected_rows` alongside the rows themselves -- a client may
6027/// ask for no rows back and still need the count, which is the usual case for
6028/// a delete.
6029///
6030/// The count is passed in rather than taken from the rows, because a nested
6031/// insert writes more rows than it returns: two articles each carrying a new
6032/// author is four rows written and two returned, and four is the answer.
6033fn mutation_result<'a>(rows: Vec<Value>, affected: usize, by_key: bool) -> Option<FieldValue<'a>> {
6034    if by_key {
6035        return rows.into_iter().next().map(FieldValue::value);
6036    }
6037    let mut response = async_graphql::indexmap::IndexMap::new();
6038    response.insert(
6039        async_graphql::Name::new("affected_rows"),
6040        Value::from(affected),
6041    );
6042    response.insert(async_graphql::Name::new("returning"), Value::List(rows));
6043    Some(FieldValue::value(Value::Object(response)))
6044}
6045
6046/// Bind a JSON value to a sqlx query.
6047fn bind_json_value<'q>(
6048    query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
6049    value: &serde_json::Value,
6050) -> sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments> {
6051    match value {
6052        serde_json::Value::Null => query.bind(None::<String>),
6053        serde_json::Value::Bool(b) => query.bind(*b),
6054        serde_json::Value::Number(n) => {
6055            if let Some(i) = n.as_i64() {
6056                query.bind(i)
6057            } else if let Some(f) = n.as_f64() {
6058                query.bind(f)
6059            } else {
6060                query.bind(n.to_string())
6061            }
6062        }
6063        serde_json::Value::String(s) => query.bind(s.clone()),
6064        _ => query.bind(value.to_string()),
6065    }
6066}
6067
6068/// Execute an update mutation.
6069#[allow(clippy::too_many_arguments)] // the predicate needs the schema it reads
6070async fn execute_update(
6071    pool: &PgPool,
6072    gql_ctx: &GraphQLContext,
6073    schema_name: &str,
6074    table_name: &str,
6075    type_name: &str,
6076    operators: Vec<(&'static str, serde_json::Value)>,
6077    column_types: HashMap<String, String>,
6078    where_clause: Option<serde_json::Value>,
6079    relationships: &HashMap<String, Vec<RelationshipField>>,
6080    names: &crate::names::NameOverrides,
6081) -> Result<Vec<Value>, async_graphql::Error> {
6082    use sqlx::Row;
6083
6084    trace!("Update mutation for {}: {:?}", table_name, operators);
6085
6086    // Build the SET clause. Each operator writes a column in terms of itself
6087    // except `_set`, which replaces it -- `_inc` adds, the jsonb operators
6088    // concatenate or remove, and a client may send several in one mutation as
6089    // long as they name different columns.
6090    let mut set_parts: Vec<String> = Vec::new();
6091    let mut set_values: Vec<serde_json::Value> = Vec::new();
6092    let mut param_idx = 1;
6093    let mut written: HashSet<String> = HashSet::new();
6094
6095    // What the permission writes whatever the request said. A preset overrides
6096    // rather than collides -- one a client could override is not a preset --
6097    // so these go in first and the operators below step around the columns
6098    // they name.
6099    //
6100    // This is also what makes an update with no `_set` at all a real update:
6101    // `update_resident(where: {...})` under a permission whose `set` names
6102    // `city` changes `city`, and answers with the rows it changed rather than
6103    // with nothing.
6104    let preset = crate::role::presets(
6105        &gql_ctx.caller(),
6106        names,
6107        schema_name,
6108        table_name,
6109        crate::role::Verb::Update,
6110    )
6111    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6112    let preset_columns: HashSet<&str> = preset.iter().map(|(name, _)| name.as_str()).collect();
6113    for (column, value) in &preset {
6114        let placeholder =
6115            write_expression(&column_types, column, value, &format!("${}", param_idx));
6116        set_parts.push(format!(
6117            "{} = {}",
6118            postrust_sql::escape_ident(column),
6119            placeholder
6120        ));
6121        set_values.push(write_operand(&column_types, column, value));
6122        param_idx += 1;
6123    }
6124
6125    for (operator, payload) in &operators {
6126        let serde_json::Value::Object(map) = payload else {
6127            return Err(async_graphql::Error::new(format!(
6128                "\"{}\" takes an object mapping columns to values",
6129                operator
6130            )));
6131        };
6132        for (field, value) in map {
6133            let column = &names
6134                .column_source(schema_name, table_name, field)
6135                .unwrap_or(field)
6136                .to_string();
6137            // A preset has this column. Not the duplicate-write error below:
6138            // the request is allowed to name it, and the permission wins.
6139            if preset_columns.contains(column.as_str()) {
6140                continue;
6141            }
6142            if !written.insert(column.clone()) {
6143                return Err(async_graphql::Error::new(format!(
6144                    "\"{}\" is written twice in one update; a column may be \
6145                     changed by one operator at a time",
6146                    column
6147                )));
6148            }
6149            let quoted = postrust_sql::escape_ident(column);
6150            // `_set` writes the column and `_inc` adds to it, so both take a
6151            // value of the column's type and are cast to it -- `money + $1`
6152            // is not an operator PostgreSQL has, and neither is `money +
6153            // double precision`, which is what a bound number arrives as. The
6154            // others already say what they need -- `::jsonb` for a
6155            // concatenation, `::text[]` for a path -- and casting twice would
6156            // be wrong for `_delete_elem`, whose operand is an integer rather
6157            // than a value of the column.
6158            let placeholder = match *operator {
6159                "_set" | "_inc" => {
6160                    write_expression(&column_types, column, value, &format!("${}", param_idx))
6161                }
6162                _ => format!("${}", param_idx),
6163            };
6164            // The assignment PostgreSQL needs, which for everything but `_set`
6165            // reads the column's current value.
6166            let assignment = match *operator {
6167                "_set" => format!("{} = {}", quoted, placeholder),
6168                "_inc" => format!("{} = {} + {}", quoted, quoted, placeholder),
6169                "_append" => format!("{} = {} || {}::jsonb", quoted, quoted, placeholder),
6170                "_prepend" => format!("{} = {}::jsonb || {}", quoted, placeholder, quoted),
6171                "_delete_key" => format!("{} = {} - {}::text", quoted, quoted, placeholder),
6172                "_delete_elem" => format!("{} = {} - {}::int", quoted, quoted, placeholder),
6173                // A path is a list of keys, and one parameter carrying
6174                // `["name","last"]` is a JSON array -- which PostgreSQL reads
6175                // as an array literal and refuses, `malformed array literal`.
6176                // The elements are bound one at a time into an `ARRAY[...]`,
6177                // the same way the key-existence comparisons take theirs.
6178                "_delete_at_path" => {
6179                    let Some(steps) = value.as_array() else {
6180                        return Err(async_graphql::Error::new(format!(
6181                            "\"_delete_at_path\" on \"{}\" takes a list of keys",
6182                            column
6183                        )));
6184                    };
6185                    let path = sql_array(steps, "text[]", &mut param_idx, &mut set_values);
6186                    set_parts.push(format!("{} = {} #- {}", quoted, quoted, path));
6187                    continue;
6188                }
6189                other => {
6190                    return Err(async_graphql::Error::new(format!(
6191                        "unsupported update operator \"{}\"",
6192                        other
6193                    )))
6194                }
6195            };
6196            if *operator == "_set"
6197                && matches!(
6198                    column_types.get(column).map(String::as_str),
6199                    Some("geometry") | Some("geography")
6200                )
6201            {
6202                check_geojson(value)?;
6203            }
6204            set_parts.push(assignment);
6205            set_values.push(match *operator {
6206                "_set" => write_operand(&column_types, column, value),
6207                _ => value.clone(),
6208            });
6209            param_idx += 1;
6210        }
6211    }
6212
6213    // An update that changes nothing changes no rows. Refusing it looks like
6214    // the stricter answer and is the wrong one: a client building `_set` from
6215    // a form the user submitted unchanged sends an empty object, and the
6216    // honest report of what happened is that nothing did.
6217    if set_parts.is_empty() {
6218        return Ok(Vec::new());
6219    }
6220
6221    // Build WHERE clause. It can follow a relationship, the same way a query's
6222    // can: `update_article(where: {author: {name: {_eq: "x"}}})` names the
6223    // rows by something they point at rather than by a column of their own.
6224    let guard = gql_ctx
6225        .schema_cache
6226        .get()
6227        .await
6228        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6229    let cache = guard
6230        .as_ref()
6231        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6232    let scope = WhereScope::table(schema_name, table_name, type_name, names)
6233        .under_alias(WRITTEN_ROW)
6234        .with_resolution(cache, relationships);
6235    let (where_sql, where_values) = build_where_clause(where_clause.as_ref(), param_idx, &scope)?;
6236
6237    // An absent or unrecognised `where` argument yields an empty clause, which
6238    // would update every row in the table. Refuse instead.
6239    if where_sql.is_empty() {
6240        return Err(async_graphql::Error::new(format!(
6241            "update on \"{}\" requires a `where` argument with at least one \
6242             recognised condition; refusing to update every row",
6243            table_name
6244        )));
6245    }
6246
6247    // What the changed row has to satisfy for the change to stand. Evaluated
6248    // in the `RETURNING` clause, against the row as written -- which is the
6249    // only place it can honestly be evaluated, and costs neither a re-read nor
6250    // a way to identify the rows afterwards.
6251    let (check_sql, check_values) = permission_check_sql(
6252        gql_ctx,
6253        names,
6254        schema_name,
6255        table_name,
6256        crate::role::Verb::Update,
6257        &scope,
6258        param_idx + where_values.len(),
6259    )?;
6260
6261    let sql = format!(
6262        "UPDATE {}.{} AS {} SET {} {} RETURNING {}{}",
6263        postrust_sql::escape_ident(schema_name),
6264        postrust_sql::escape_ident(table_name),
6265        postrust_sql::escape_ident(WRITTEN_ROW),
6266        set_parts.join(", "),
6267        where_sql,
6268        row_json(&postrust_sql::escape_ident(WRITTEN_ROW), &column_types),
6269        check_sql
6270    );
6271
6272    trace!("Executing UPDATE SQL: {}", sql);
6273
6274    // Build query with parameters
6275    let mut query = sqlx::query(&sql);
6276
6277    // Bind SET values
6278    for val in &set_values {
6279        query = bind_json_value(query, val);
6280    }
6281
6282    // Bind WHERE values
6283    for val in &where_values {
6284        query = bind_json_value(query, val);
6285    }
6286
6287    // And the check's, last, because `RETURNING` is.
6288    for val in &check_values {
6289        query = bind_json_value(query, val);
6290    }
6291
6292    let mut guard = write_tx(gql_ctx, pool).await?;
6293    let conn = guard.as_mut().expect("write_tx opens one");
6294    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6295
6296    refuse_unchecked_rows(&rows, !check_sql.is_empty())?;
6297
6298    let updated: Vec<Value> = rows
6299        .iter()
6300        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6301        .map(json_to_value)
6302        .collect();
6303
6304    Ok(updated)
6305}
6306
6307/// A permission's check, as a column of the write's `RETURNING`.
6308///
6309/// Empty where nothing has to be checked. Otherwise `, (<predicate>) AS ...`,
6310/// evaluated per written row against the row itself -- so a row the permission
6311/// refuses is known before the transaction is settled, and settling it is
6312/// already a rollback once an error is reported.
6313fn permission_check_sql(
6314    gql_ctx: &GraphQLContext,
6315    names: &crate::names::NameOverrides,
6316    schema_name: &str,
6317    table_name: &str,
6318    verb: crate::role::Verb,
6319    scope: &WhereScope<'_>,
6320    start_param_idx: usize,
6321) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6322    let check = crate::role::write_check(&gql_ctx.caller(), names, schema_name, table_name, verb)
6323        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6324    let Some(check) = check else {
6325        return Ok((String::new(), Vec::new()));
6326    };
6327
6328    let mut values = Vec::new();
6329    let mut param_idx = start_param_idx;
6330    let mut alias_counter = 0usize;
6331    let condition = build_condition(
6332        &check,
6333        scope,
6334        &mut param_idx,
6335        &mut values,
6336        &mut alias_counter,
6337    )?;
6338    Ok(match condition {
6339        // A check that compiles to nothing restricts nothing, which is what an
6340        // empty one says.
6341        None => (String::new(), Vec::new()),
6342        Some(sql) => (
6343            format!(
6344                ", ({}) AS {}",
6345                sql,
6346                postrust_sql::escape_ident(CHECK_COLUMN)
6347            ),
6348            values,
6349        ),
6350    })
6351}
6352
6353/// The column a permission's check is returned in.
6354const CHECK_COLUMN: &str = "pgrst_permission_check";
6355
6356/// Refuse the whole write if any row it produced fails the permission's check.
6357///
6358/// Every row, not the first: a mutation is one transaction and a single
6359/// refused row takes the rest with it, which is what makes a check a rule
6360/// about the write rather than about a row. A null counts as a failure -- an
6361/// unknown is not a satisfied condition.
6362fn refuse_unchecked_rows(
6363    rows: &[sqlx::postgres::PgRow],
6364    checked: bool,
6365) -> Result<(), async_graphql::Error> {
6366    if !checked {
6367        return Ok(());
6368    }
6369    use sqlx::Row;
6370    let all_passed = rows
6371        .iter()
6372        .all(|row| row.try_get::<bool, _>(CHECK_COLUMN).unwrap_or(false));
6373    match all_passed {
6374        true => Ok(()),
6375        false => Err(coded_error("permission-error", crate::role::CHECK_FAILED)),
6376    }
6377}
6378
6379/// Execute a delete mutation.
6380#[allow(clippy::too_many_arguments)] // the projection needs the schema it reads
6381async fn execute_delete(
6382    pool: &PgPool,
6383    gql_ctx: &GraphQLContext,
6384    schema_name: &str,
6385    table_name: &str,
6386    type_name: &str,
6387    where_clause: Option<serde_json::Value>,
6388    returning: Option<async_graphql::SelectionField<'_>>,
6389    relationships: &HashMap<String, Vec<RelationshipField>>,
6390    names: &crate::names::NameOverrides,
6391    max_rows: Option<i64>,
6392) -> Result<Vec<Value>, async_graphql::Error> {
6393    use sqlx::Row;
6394
6395    trace!("Delete mutation for {}", table_name);
6396
6397    let guard = gql_ctx
6398        .schema_cache
6399        .get()
6400        .await
6401        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6402    let cache = guard
6403        .as_ref()
6404        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6405    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
6406    let column_types = cache
6407        .get_table(&qi)
6408        .map(|table| exposed_column_types(table, names))
6409        .unwrap_or_default();
6410
6411    // Build WHERE clause. It can follow a relationship -- `delete_article(where:
6412    // {author: {id: {_eq: 2}}})` is the article-by-author delete anyone writes
6413    // -- so the scope is given the schema to resolve one with.
6414    let scope = WhereScope::table(schema_name, table_name, type_name, names)
6415        .under_alias(WRITTEN_ROW)
6416        .with_resolution(cache, relationships);
6417    let (where_sql, mut values) = build_where_clause(where_clause.as_ref(), 1, &scope)?;
6418
6419    // An absent or unrecognised `where` argument yields an empty clause, which
6420    // would delete every row in the table. Refuse instead.
6421    if where_sql.is_empty() {
6422        return Err(async_graphql::Error::new(format!(
6423            "delete on \"{}\" requires a `where` argument with at least one \
6424             recognised condition; refusing to delete every row",
6425            table_name
6426        )));
6427    }
6428
6429    // A relationship or computed field asked for in `returning` cannot be read
6430    // afterwards -- the rows are gone by then, which is why this used to answer
6431    // the plain columns and then fail on a non-null list. Inside one statement
6432    // the deleted rows are still a table: the delete goes in a CTE and the
6433    // projection reads from it, while the rows it points at are still there.
6434    let mut projection = String::new();
6435    let renamed = cache
6436        .get_table(&qi)
6437        .and_then(|table| rename_projection(table, "src", names));
6438    if let Some(returning) = returning {
6439        let mut param_idx = values.len() + 1;
6440        let mut alias_counter = 0usize;
6441        let embeds = build_embed_expressions(
6442            &gql_ctx.caller(),
6443            cache,
6444            relationships,
6445            type_name,
6446            "src",
6447            returning.selection_set(),
6448            max_rows,
6449            &mut alias_counter,
6450            &mut param_idx,
6451            &mut values,
6452            names,
6453        )?;
6454        let computed = match cache.get_table(&qi) {
6455            Some(table) => computed_projections(
6456                table,
6457                returning.selection_set(),
6458                "src",
6459                names,
6460                cache,
6461                &mut param_idx,
6462                &mut values,
6463            )?,
6464            None => Vec::new(),
6465        };
6466        for expression in &computed {
6467            projection.push_str(", ");
6468            projection.push_str(expression);
6469        }
6470        for (name, expression) in &embeds {
6471            projection.push_str(", ");
6472            projection.push_str(expression);
6473            projection.push_str(" AS ");
6474            projection.push_str(&postrust_sql::escape_ident(name));
6475        }
6476    }
6477
6478    let written = postrust_sql::escape_ident(WRITTEN_ROW);
6479    let sql = if projection.is_empty() && renamed.is_none() {
6480        format!(
6481            "DELETE FROM {}.{} AS {} {} RETURNING {}",
6482            postrust_sql::escape_ident(schema_name),
6483            postrust_sql::escape_ident(table_name),
6484            written,
6485            where_sql,
6486            row_json(&written, &column_types)
6487        )
6488    } else {
6489        format!(
6490            "WITH pgrst_deleted AS (DELETE FROM {}.{} AS {} {} RETURNING {}.*) \
6491             SELECT {} FROM (SELECT {}{} FROM pgrst_deleted AS src) AS pgrst_r",
6492            postrust_sql::escape_ident(schema_name),
6493            postrust_sql::escape_ident(table_name),
6494            written,
6495            where_sql,
6496            written,
6497            row_json("pgrst_r", &column_types),
6498            renamed.unwrap_or_else(|| "src.*".to_string()),
6499            projection
6500        )
6501    };
6502
6503    trace!("Executing DELETE SQL: {}", sql);
6504
6505    let mut query = sqlx::query(&sql);
6506    for val in &values {
6507        query = bind_json_value(query, val);
6508    }
6509
6510    let mut guard = write_tx(gql_ctx, pool).await?;
6511    let conn = guard.as_mut().expect("write_tx opens one");
6512    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6513
6514    let deleted: Vec<Value> = rows
6515        .iter()
6516        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6517        .map(json_to_value)
6518        .collect();
6519
6520    Ok(deleted)
6521}
6522
6523/// The predicate a role's permission adds to every read of one table.
6524///
6525/// `None` where nothing restricts the read: no permission document, an
6526/// administrator, or a permission whose filter is `{}`. The value is a
6527/// `<table>_bool_exp` like any other, because [`crate::role::permission_where`]
6528/// rewrites it into one -- which is what lets the same compiler serve both and
6529/// keeps a permission able to say everything a `where` can.
6530fn permission_predicate(
6531    caller: &crate::role::Caller<'_>,
6532    names: &crate::names::NameOverrides,
6533    schema: &str,
6534    table: &str,
6535) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6536    permission_filter(caller, names, schema, table, crate::role::Verb::Select)
6537}
6538
6539/// The same, for whichever grant the operation is asking under.
6540fn permission_filter(
6541    caller: &crate::role::Caller<'_>,
6542    names: &crate::names::NameOverrides,
6543    schema: &str,
6544    table: &str,
6545    verb: crate::role::Verb,
6546) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6547    crate::role::row_filter(caller, names, schema, table, verb)
6548        .map(|filter| filter.map(|predicate| serde_json::json!({ PERMISSION: predicate })))
6549        .map_err(|fault| coded_error(fault.code(), fault.to_string()))
6550}
6551
6552/// The key a permission's own predicate is wrapped in on its way to the
6553/// compiler.
6554///
6555/// The two halves of a `where` are the same language and are compiled by the
6556/// same code, which is what keeps a permission able to say everything a client
6557/// can. They are not evaluated by the same party: a client's half asks what
6558/// *this caller* may see, and a permission's half is the server's own
6559/// predicate, evaluated as the server.
6560///
6561/// The difference shows on a relationship. `{"Artist": {"id":
6562/// "X-Hasura-Artist-Id"}}` on `Track` grants the tracks belonging to the
6563/// caller's artist -- to a role that has no permission on `Artist` at all, and
6564/// is not meant to have one: the predicate consults that table, it does not
6565/// expose it. Compiled as the caller, it needs a permission that does not
6566/// exist and refuses itself; compiled as the server, it says what it was
6567/// written to say. `_exists` has always worked this way and says so where it
6568/// is built; this is the same rule for the relationship a permission follows.
6569///
6570/// The name cannot collide with a column: no boolean expression a client can
6571/// write has this field, because no input type declares it.
6572const PERMISSION: &str = "_permission";
6573
6574/// How many rows a permission lets one read return.
6575///
6576/// A ceiling rather than a default: a request asking for more gets this, one
6577/// asking for fewer gets what it asked for, and one asking for nothing gets
6578/// the ceiling. The same rule `PGRST_MAX_ROWS` follows, folded with it.
6579fn permission_limit(
6580    caller: &crate::role::Caller<'_>,
6581    names: &crate::names::NameOverrides,
6582    schema: &str,
6583    table: &str,
6584) -> Option<i64> {
6585    crate::role::read_limit(caller, names, schema, table)
6586}
6587
6588/// Both predicates, as one.
6589///
6590/// Written as an `_and` rather than as two clauses so that everything
6591/// downstream -- parameter numbering, relationship resolution, the `EXISTS`
6592/// that a relationship predicate becomes -- happens once, in one place, for
6593/// both halves.
6594fn and_predicates(
6595    request: Option<serde_json::Value>,
6596    permission: Option<serde_json::Value>,
6597) -> Option<serde_json::Value> {
6598    match (request, permission) {
6599        (None, None) => None,
6600        (Some(only), None) | (None, Some(only)) => Some(only),
6601        (Some(request), Some(permission)) => {
6602            Some(serde_json::json!({ "_and": [request, permission] }))
6603        }
6604    }
6605}
6606
6607/// Build a WHERE clause from a boolean expression.
6608///
6609/// The expression is the JSON form of a `<table>_bool_exp`: column names
6610/// mapping to comparisons, and `_and`, `_or` and `_not` for structure. It
6611/// nests, so this recurses.
6612///
6613/// Nothing is dropped silently. An operator that is not recognised widens the
6614/// result set if it is ignored -- every row for a query, every row for a
6615/// mutation -- so it is an error instead.
6616fn build_where_clause(
6617    where_value: Option<&serde_json::Value>,
6618    start_param_idx: usize,
6619    scope: &WhereScope<'_>,
6620) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6621    let mut values: Vec<serde_json::Value> = Vec::new();
6622    let mut param_idx = start_param_idx;
6623    let mut alias_counter = 0usize;
6624
6625    let condition = match where_value {
6626        Some(value) => build_condition(
6627            value,
6628            scope,
6629            &mut param_idx,
6630            &mut values,
6631            &mut alias_counter,
6632        )?,
6633        None => None,
6634    };
6635
6636    Ok(match condition {
6637        Some(sql) => (format!("WHERE {}", sql), values),
6638        None => (String::new(), values),
6639    })
6640}
6641
6642/// The table a boolean expression is being read against.
6643///
6644/// Column references are qualified with `sql_ref` so that a predicate over a
6645/// relationship -- which becomes a correlated `EXISTS` against another table --
6646/// can tell the two apart. Without the qualification a child column sharing a
6647/// parent column's name would silently resolve to whichever PostgreSQL
6648/// preferred.
6649///
6650/// `resolution` is absent where a caller has no schema cache to hand, which is
6651/// how the mutation paths are called. A relationship predicate then reports
6652/// that it cannot be resolved rather than being read as a column.
6653pub struct WhereScope<'a> {
6654    /// The table itself, for the comparisons that need a column's type.
6655    qualified: postrust_core::api_request::QualifiedIdentifier,
6656    /// How to refer to this table's columns in SQL.
6657    sql_ref: String,
6658    /// How to refer to this table's *row*, which is what a function taking the
6659    /// row is passed. A qualified name reads as a column reference there, so
6660    /// this is the bare alias.
6661    row_ref: String,
6662    /// The GraphQL type name, which is the key into the relationship map.
6663    type_name: String,
6664    /// How to refer to the columns of the table the predicate started at, and
6665    /// which table that is.
6666    ///
6667    /// A column comparison written `["$", "name"]` means that table's column,
6668    /// however many relationships the predicate has descended through since --
6669    /// which is what makes the form worth having. Equal to this table's own
6670    /// until an `EXISTS` is entered.
6671    root_ref: String,
6672    root_qualified: postrust_core::api_request::QualifiedIdentifier,
6673    /// The names this table's columns are exposed under, so a predicate
6674    /// written against a field can be written as SQL against a column.
6675    names: &'a crate::names::NameOverrides,
6676    resolution: Option<WhereResolution<'a>>,
6677    /// Who is asking, so that a predicate following a relationship narrows the
6678    /// far side to what that caller may read.
6679    ///
6680    /// Unrestricted unless a scope is told otherwise, which is what leaves
6681    /// every path that has no caller to hand -- the mutation ones -- behaving
6682    /// as it did.
6683    caller: Option<crate::role::Caller<'a>>,
6684}
6685
6686/// What a scope needs to follow a relationship.
6687struct WhereResolution<'a> {
6688    cache: &'a SchemaCache,
6689    relationships: &'a HashMap<String, Vec<RelationshipField>>,
6690}
6691
6692impl<'a> WhereScope<'a> {
6693    /// A scope over a table addressed by its qualified name.
6694    pub fn table(
6695        schema: &str,
6696        table: &str,
6697        type_name: &str,
6698        names: &'a crate::names::NameOverrides,
6699    ) -> Self {
6700        Self {
6701            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6702            sql_ref: format!(
6703                "{}.{}",
6704                postrust_sql::escape_ident(schema),
6705                postrust_sql::escape_ident(table)
6706            ),
6707            row_ref: postrust_sql::escape_ident(table),
6708            type_name: type_name.to_string(),
6709            root_ref: format!(
6710                "{}.{}",
6711                postrust_sql::escape_ident(schema),
6712                postrust_sql::escape_ident(table)
6713            ),
6714            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6715            names,
6716            resolution: None,
6717            caller: None,
6718        }
6719    }
6720
6721    /// The same, referred to by an alias rather than by its name.
6722    ///
6723    /// A statement that writes a table has to name that table's row in
6724    /// `RETURNING`, and a bare table name there is read as a *column* first --
6725    /// so `area.area`, a geography column in a table of the same name, made
6726    /// `to_jsonb("area")` the shape rather than the row. The alias is a name
6727    /// no column has.
6728    fn under_alias(mut self, alias: &str) -> Self {
6729        // The root moves with it: this is the outermost table being renamed,
6730        // not a step into another one.
6731        if self.root_ref == self.sql_ref {
6732            self.root_ref = postrust_sql::escape_ident(alias);
6733        }
6734        self.sql_ref = postrust_sql::escape_ident(alias);
6735        self.row_ref = postrust_sql::escape_ident(alias);
6736        self
6737    }
6738
6739    /// The same, able to follow relationships.
6740    fn with_resolution(
6741        mut self,
6742        cache: &'a SchemaCache,
6743        relationships: &'a HashMap<String, Vec<RelationshipField>>,
6744    ) -> Self {
6745        self.resolution = Some(WhereResolution {
6746            cache,
6747            relationships,
6748        });
6749        self
6750    }
6751
6752    /// A scope over an aliased table, able to follow relationships.
6753    #[allow(clippy::too_many_arguments)]
6754    fn for_alias(
6755        schema: &str,
6756        table: &str,
6757        alias: &str,
6758        type_name: &str,
6759        cache: &'a SchemaCache,
6760        relationships: &'a HashMap<String, Vec<RelationshipField>>,
6761        names: &'a crate::names::NameOverrides,
6762    ) -> Self {
6763        Self {
6764            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6765            sql_ref: postrust_sql::escape_ident(alias),
6766            row_ref: postrust_sql::escape_ident(alias),
6767            type_name: type_name.to_string(),
6768            root_ref: postrust_sql::escape_ident(alias),
6769            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6770            names,
6771            resolution: Some(WhereResolution {
6772                cache,
6773                relationships,
6774            }),
6775            caller: None,
6776        }
6777    }
6778
6779    /// A scope over an aliased table, for the inside of an `EXISTS`.
6780    /// A scope over the *other* table of a relationship, under an alias.
6781    ///
6782    /// The child's own qualified name, not the parent's: a comparison inside
6783    /// an `EXISTS` is against the child's columns, so it is the child's types
6784    /// and the child's renames that decide how to write it.
6785    fn aliased(
6786        schema: &str,
6787        table: &str,
6788        alias: &str,
6789        type_name: &str,
6790        from: &WhereScope<'a>,
6791    ) -> Self {
6792        Self {
6793            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6794            sql_ref: postrust_sql::escape_ident(alias),
6795            row_ref: postrust_sql::escape_ident(alias),
6796            type_name: type_name.to_string(),
6797            // Carried, not replaced: `$` names the table the predicate began
6798            // at, which is the whole point of descending with it.
6799            root_ref: from.root_ref.clone(),
6800            root_qualified: from.root_qualified.clone(),
6801            names: from.names,
6802            resolution: from.resolution.as_ref().map(|r| WhereResolution {
6803                cache: r.cache,
6804                relationships: r.relationships,
6805            }),
6806            // Carried across the relationship: the caller does not change on
6807            // the way into an EXISTS, and the far side has its own permission
6808            // to be narrowed by.
6809            caller: from.caller,
6810        }
6811    }
6812
6813    /// Narrow what this scope's predicates may reach to one caller.
6814    pub fn for_caller(mut self, caller: crate::role::Caller<'a>) -> Self {
6815        self.caller = Some(caller);
6816        self
6817    }
6818
6819    /// The same scope with no caller: what a permission's own predicate is
6820    /// evaluated as. See `PERMISSION`.
6821    fn as_system(&self) -> WhereScope<'a> {
6822        WhereScope {
6823            qualified: self.qualified.clone(),
6824            sql_ref: self.sql_ref.clone(),
6825            row_ref: self.row_ref.clone(),
6826            type_name: self.type_name.clone(),
6827            // The root moves here. `$` in a permission names the table the
6828            // permission is *written on*, which is this one -- not whatever
6829            // table the query happened to start at. Reading `dml_rbp_tasks`
6830            // through a project's `where: {tasks: {...}}` compared the task's
6831            // grant against a column of `dml_rbp_projects` until this, and
6832            // answered with rows the role may not see.
6833            root_ref: self.sql_ref.clone(),
6834            root_qualified: self.qualified.clone(),
6835            names: self.names,
6836            resolution: self.resolution.as_ref().map(|r| WhereResolution {
6837                cache: r.cache,
6838                relationships: r.relationships,
6839            }),
6840            caller: None,
6841        }
6842    }
6843
6844    /// What a field name refers to, as SQL: a column of this table, or the
6845    /// call that produces a computed field.
6846    ///
6847    /// A computed field is in the boolean expression because it is on the type
6848    /// -- `where: {sum_float_offset: {_gt: 5}}` is a question a client can
6849    /// write -- and reading it as a column answered `column
6850    /// float_test.sum_float_offset does not exist`.
6851    fn column(&self, name: &str) -> String {
6852        let column = self.column_name(name);
6853        let plain = format!("{}.{}", self.sql_ref, postrust_sql::escape_ident(column));
6854        let Some(cache) = self.resolution.as_ref().map(|r| r.cache) else {
6855            return plain;
6856        };
6857        let Some(table) = cache.get_table(&self.qualified) else {
6858            return plain;
6859        };
6860        if table.get_column(column).is_some() {
6861            return plain;
6862        }
6863        let function = self
6864            .names
6865            .computed_source(&table.schema, &table.name, name)
6866            .unwrap_or(name);
6867        match table.get_computed_column(function) {
6868            Some(definition) => {
6869                computed_call(definition, &format!("{}.*", self.row_ref), &self.row_ref)
6870            }
6871            None => plain,
6872        }
6873    }
6874
6875    /// The column behind a field name.
6876    fn column_name<'n>(&'n self, name: &'n str) -> &'n str {
6877        self.names
6878            .column_source(&self.qualified.schema, &self.qualified.name, name)
6879            .unwrap_or(name)
6880    }
6881
6882    /// The PostgreSQL type of one of this table's columns, where it can be
6883    /// found. A spatial comparison needs it: the same function takes a
6884    /// geometry or a geography and the operand has to be cast to match.
6885    fn column_type(&self, name: &str) -> Option<String> {
6886        let cache = self.resolution.as_ref()?.cache;
6887        let table = cache.get_table(&self.qualified)?;
6888        table
6889            .get_column(self.column_name(name))
6890            .map(|c| c.nominal_type.clone())
6891    }
6892
6893    /// The other side of a column-to-column comparison, as SQL.
6894    ///
6895    /// Hasura writes the operand two ways. A bare name is a column of *this*
6896    /// table -- `{"bid_price": {"$cgt": "price"}}` compares two columns of one
6897    /// auction. `["$", "name"]` is a column of the table the permission is
6898    /// written on, which is what makes the form useful inside a predicate over
6899    /// a relationship: `{"item": {"item_quantity": {"_cgte": ["$",
6900    /// "order_cart_quantity"]}}}` asks whether the item has as many in stock
6901    /// as the cart is asking for, and the cart is the outer row.
6902    ///
6903    /// Nothing else is accepted. A longer path would walk relationships to
6904    /// reach the column, which Hasura allows and no case here writes -- and
6905    /// reading it wrongly would widen a permission rather than narrow it.
6906    fn other_column(
6907        &self,
6908        operand: &serde_json::Value,
6909        op: &str,
6910    ) -> Result<String, async_graphql::Error> {
6911        use serde_json::Value;
6912
6913        let refused = |what: &str| {
6914            async_graphql::Error::new(format!(
6915                "the \"{}\" comparison takes the name of a column, {}",
6916                op, what
6917            ))
6918        };
6919        let (against, qualified, name) = match operand {
6920            Value::String(name) => (&self.sql_ref, &self.qualified, name.as_str()),
6921            Value::Array(path) => match path.as_slice() {
6922                [Value::String(name)] => (&self.sql_ref, &self.qualified, name.as_str()),
6923                [Value::String(root), Value::String(name)] if root == "$" => {
6924                    (&self.root_ref, &self.root_qualified, name.as_str())
6925                }
6926                _ => return Err(refused("or [\"$\", \"a column\"]")),
6927            },
6928            _ => return Err(refused("written as a string")),
6929        };
6930        // Read against whichever table it belongs to, since a rename is
6931        // per-table and the two are not the same table.
6932        let column = self
6933            .names
6934            .column_source(&qualified.schema, &qualified.name, name)
6935            .unwrap_or(name);
6936        Ok(format!(
6937            "{}.{}",
6938            against,
6939            postrust_sql::escape_ident(column)
6940        ))
6941    }
6942
6943    /// The relationship a predicate is following, where there is one.
6944    ///
6945    /// Looked up in the schema's own map first, which is the answer for every
6946    /// relationship the role can read. A permission may name one it cannot:
6947    /// role `Artist` reads `Track` and filters it by `{"Artist": {"id": ...}}`
6948    /// while having no permission on `Artist` at all, so that relationship is
6949    /// in no schema this role is served. The catalogue still has it -- the
6950    /// cache a scope resolves against is the unreduced one -- and the
6951    /// relationship is derived from it there.
6952    ///
6953    /// Deriving it cannot let a client reach a hidden table: a key that is not
6954    /// a field of the boolean expression is refused before any of this runs,
6955    /// and the only writer of such a key is a permission.
6956    fn relationship(&self, name: &str) -> Option<std::borrow::Cow<'a, RelationshipField>> {
6957        let resolution = self.resolution.as_ref()?;
6958        let published = resolution
6959            .relationships
6960            .get(&self.type_name)
6961            .and_then(|fields| fields.iter().find(|r| r.name == name));
6962        if let Some(found) = published {
6963            return Some(std::borrow::Cow::Borrowed(found));
6964        }
6965
6966        let table = resolution.cache.get_table(&self.qualified)?;
6967
6968        // A relationship metadata declares, which may be a mapped join the
6969        // catalogue has never heard of. `message.members` is one, and a
6970        // permission on `message` is written through it -- so this is not a
6971        // convenience: without it that filter cannot be compiled at all and
6972        // the read is refused rather than narrowed.
6973        if let Some(declared) = self
6974            .names
6975            .declared_relationships(&table.schema, &table.name)
6976        {
6977            if let Some(entry) = declared.iter().find(|entry| entry.name == name) {
6978                // Reached by a mapping, which the catalogue has never heard
6979                // of.
6980                let target = entry
6981                    .target(&table.schema)
6982                    .map(|(_, target)| target)
6983                    .unwrap_or_default();
6984                if let Some(field) = crate::schema::mapped_relationship_field(
6985                    entry,
6986                    table,
6987                    resolution.cache,
6988                    &table.schema,
6989                    &target,
6990                    &target,
6991                    &target,
6992                ) {
6993                    return Some(std::borrow::Cow::Owned(field));
6994                }
6995                // Or by a key, under the name the declaration gives it --
6996                // which is the only place that name is written down now that
6997                // a declaration carries it.
6998                if let Some(key) = entry.using.as_deref() {
6999                    if let Some(found) = resolution
7000                        .cache
7001                        .get_relationships(&self.qualified, &table.schema)
7002                        .into_iter()
7003                        .flatten()
7004                        .find(|rel| {
7005                            crate::schema::relationship_keys(rel)
7006                                .iter()
7007                                .any(|found| found == key)
7008                        })
7009                    {
7010                        return Some(std::borrow::Cow::Owned(
7011                            crate::schema::relationship::RelationshipField::from_relationship_named(
7012                                found,
7013                                &found.foreign_table().name,
7014                                Some(&entry.name),
7015                            ),
7016                        ));
7017                    }
7018                }
7019            }
7020        }
7021
7022        resolution
7023            .cache
7024            .get_relationships(&self.qualified, &table.schema)?
7025            .iter()
7026            .find_map(|rel| {
7027                let given = crate::schema::relationship_keys(rel)
7028                    .iter()
7029                    .find_map(|key| self.names.relationship(&table.schema, &table.name, key));
7030                // The target's own name stands in for its GraphQL type, which
7031                // this role has none of. Nothing reads it but the lookup
7032                // above, and that lookup will land here again.
7033                let field = RelationshipField::from_relationship_named(
7034                    rel,
7035                    &rel.foreign_table().name,
7036                    given,
7037                );
7038                (field.name == name).then_some(std::borrow::Cow::Owned(field))
7039            })
7040    }
7041}
7042
7043/// The SQL operator behind a column-to-column comparison, where this is one.
7044///
7045/// `_ceq`, `_cne`, `_cgt`, `_cgte`, `_clt`, `_clte`: the `c` is for column.
7046/// Only a permission writes these -- no boolean expression a client can write
7047/// has them, because a client has no way to name the row a comparison would be
7048/// against. A permission does: `{"bid_price": {"$cgt": "price"}}` on `auction`
7049/// grants the rows whose bid has passed the reserve, which is a fact about the
7050/// row rather than about the caller.
7051fn column_comparison(op: &str) -> Option<&'static str> {
7052    Some(match op {
7053        "_ceq" => "=",
7054        "_cne" => "<>",
7055        "_cgt" => ">",
7056        "_cgte" => ">=",
7057        "_clt" => "<",
7058        "_clte" => "<=",
7059        _ => return None,
7060    })
7061}
7062
7063/// One boolean expression, as SQL. `None` where it constrains nothing.
7064fn build_condition(
7065    value: &serde_json::Value,
7066    scope: &WhereScope<'_>,
7067    param_idx: &mut usize,
7068    values: &mut Vec<serde_json::Value>,
7069    alias_counter: &mut usize,
7070) -> Result<Option<String>, async_graphql::Error> {
7071    let serde_json::Value::Object(map) = value else {
7072        return Ok(None);
7073    };
7074
7075    let mut conditions: Vec<String> = Vec::new();
7076
7077    for (key, val) in map {
7078        match key.as_str() {
7079            // An empty `_and` is true and an empty `_or` is false, which is
7080            // what SQL's own identities give: nothing to AND constrains
7081            // nothing, nothing to OR matches nothing.
7082            "_and" | "_or" => {
7083                let members = val.as_array().ok_or_else(|| {
7084                    async_graphql::Error::new(format!("\"{}\" takes a list of expressions", key))
7085                })?;
7086                let mut parts = Vec::new();
7087                for member in members {
7088                    if let Some(sql) =
7089                        build_condition(member, scope, param_idx, values, alias_counter)?
7090                    {
7091                        parts.push(sql);
7092                    }
7093                }
7094                if parts.is_empty() {
7095                    if key == "_or" {
7096                        conditions.push("false".to_string());
7097                    }
7098                    continue;
7099                }
7100                let joiner = if key == "_and" { " AND " } else { " OR " };
7101                conditions.push(format!("({})", parts.join(joiner)));
7102            }
7103            "_not" => {
7104                if let Some(sql) = build_condition(val, scope, param_idx, values, alias_counter)? {
7105                    conditions.push(format!("NOT ({})", sql));
7106                }
7107            }
7108            // A permission's own predicate, which is the server's rather
7109            // than the caller's. See `PERMISSION`.
7110            PERMISSION => {
7111                if let Some(sql) =
7112                    build_condition(val, &scope.as_system(), param_idx, values, alias_counter)?
7113                {
7114                    conditions.push(sql);
7115                }
7116            }
7117            // A question about a table nothing relates this one to. Only a
7118            // permission writes it; no client can, because no boolean
7119            // expression has the field.
7120            "_exists" => {
7121                conditions.push(table_exists_sql(
7122                    val,
7123                    scope,
7124                    param_idx,
7125                    values,
7126                    alias_counter,
7127                )?);
7128            }
7129            // A relationship is filtered by filtering the rows at its other
7130            // end. `where: {articles: {title: {_eq: "x"}}}` keeps the authors
7131            // that have such an article -- an `EXISTS` correlated back to the
7132            // parent row, which is also what it means for a to-one
7133            // relationship, where the subquery simply cannot match twice.
7134            name if scope.relationship(name).is_some() => {
7135                let relationship = scope.relationship(name).expect("just checked");
7136                conditions.push(exists_sql(
7137                    &relationship,
7138                    val,
7139                    scope,
7140                    param_idx,
7141                    values,
7142                    alias_counter,
7143                )?);
7144            }
7145            // A question about the whole related set rather than about any
7146            // one row of it. `where: {articles_aggregate: {count: {predicate:
7147            // {_gt: 2}}}}` keeps the authors with more than two articles,
7148            // which no `EXISTS` over one article can say.
7149            name if scope.column_type(name).is_none()
7150                && name
7151                    .strip_suffix("_aggregate")
7152                    .and_then(|rel| scope.relationship(rel))
7153                    .is_some_and(|rel| rel.is_list) =>
7154            {
7155                let relationship = name
7156                    .strip_suffix("_aggregate")
7157                    .and_then(|rel| scope.relationship(rel))
7158                    .expect("just checked");
7159                conditions.push(aggregate_predicate_sql(
7160                    &relationship,
7161                    val,
7162                    scope,
7163                    param_idx,
7164                    values,
7165                    alias_counter,
7166                )?);
7167            }
7168            column => {
7169                let quoted = scope.column(column);
7170                match val {
7171                    serde_json::Value::Object(ops) => {
7172                        let column_type = scope.column_type(column);
7173                        for (op, operand) in ops {
7174                            // A comparison against another column rather than
7175                            // against a value, which only a permission can
7176                            // write. It needs the scope to say *which* column,
7177                            // so it is answered here rather than in
7178                            // `comparison_sql`, which sees one side.
7179                            if let Some(other) = column_comparison(op) {
7180                                conditions.push(format!(
7181                                    "{} {} {}",
7182                                    quoted,
7183                                    other,
7184                                    scope.other_column(operand, op)?
7185                                ));
7186                                continue;
7187                            }
7188                            conditions.push(comparison_sql(
7189                                &quoted,
7190                                column,
7191                                column_type.as_deref(),
7192                                op,
7193                                operand,
7194                                param_idx,
7195                                values,
7196                            )?);
7197                        }
7198                    }
7199                    // `{id: 1}` rather than `{id: {_eq: 1}}`. Not a spelling
7200                    // Hasura accepts, but one that costs nothing to read and
7201                    // that hand-written queries reach for.
7202                    other => {
7203                        conditions.push(format!("{} = ${}", quoted, param_idx));
7204                        values.push(other.clone());
7205                        *param_idx += 1;
7206                    }
7207                }
7208            }
7209        }
7210    }
7211
7212    Ok(if conditions.is_empty() {
7213        None
7214    } else if conditions.len() == 1 {
7215        conditions.pop()
7216    } else {
7217        Some(conditions.join(" AND "))
7218    })
7219}
7220
7221/// A predicate over a relationship, as a correlated `EXISTS`.
7222///
7223/// Only a plain key join is expressible this way. A many-to-many through a
7224/// junction and a computed relationship both need more than a pair of columns
7225/// to correlate on, and saying so is the only honest answer -- reading the
7226/// predicate as though it constrained nothing would return every parent row.
7227fn exists_sql(
7228    relationship: &RelationshipField,
7229    child_expression: &serde_json::Value,
7230    scope: &WhereScope<'_>,
7231    param_idx: &mut usize,
7232    values: &mut Vec<serde_json::Value>,
7233    alias_counter: &mut usize,
7234) -> Result<String, async_graphql::Error> {
7235    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7236        async_graphql::Error::new(format!(
7237            "filtering on the relationship \"{}\" is not available here",
7238            relationship.name
7239        ))
7240    })?;
7241
7242    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7243        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7244
7245    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7246        return Err(async_graphql::Error::new(format!(
7247            "filtering on \"{}\" is not supported: it is reached through a junction \
7248             rather than by a key",
7249            relationship.name
7250        )));
7251    }
7252
7253    *alias_counter += 1;
7254    let alias = format!("pgrst_rel_{}", alias_counter);
7255    let child_scope = WhereScope::aliased(
7256        &plan.foreign_schema,
7257        &plan.foreign_table,
7258        &alias,
7259        &relationship.target_type,
7260        scope,
7261    );
7262
7263    // A computed relationship is correlated by argument -- the function takes
7264    // the parent row -- where a key relationship is correlated by columns.
7265    let (source, mut correlation) = match &plan.function {
7266        Some(function) => (
7267            format!(
7268                "{}.{}({})",
7269                postrust_sql::escape_ident(&function.schema),
7270                postrust_sql::escape_ident(&function.name),
7271                scope.row_ref
7272            ),
7273            Vec::new(),
7274        ),
7275        None => {
7276            let mut columns = Vec::with_capacity(plan.columns.len());
7277            for (parent_column, child_column) in &plan.columns {
7278                columns.push(format!(
7279                    "{} = {}",
7280                    scope.column(parent_column),
7281                    child_scope.column(child_column)
7282                ));
7283            }
7284            (
7285                format!(
7286                    "{}.{}",
7287                    postrust_sql::escape_ident(&plan.foreign_schema),
7288                    postrust_sql::escape_ident(&plan.foreign_table)
7289                ),
7290                columns,
7291            )
7292        }
7293    };
7294
7295    let child_condition = build_condition(
7296        child_expression,
7297        &child_scope,
7298        param_idx,
7299        values,
7300        alias_counter,
7301    )?;
7302    if let Some(sql) = child_condition {
7303        correlation.push(format!("({})", sql));
7304    }
7305
7306    // The far side's own permission. `where: {author: {name: {_eq: "Ann"}}}`
7307    // asks whether a related row exists, and a row this caller may not read is
7308    // one it may not learn the existence of either -- so the child's filter
7309    // narrows the EXISTS exactly as it narrows a read of the same table.
7310    if let Some(caller) = &child_scope.caller {
7311        // Through `permission_predicate` rather than `role::read_predicate`,
7312        // so the far side's filter is marked as the server's own -- see
7313        // `PERMISSION`. Without the mark it was compiled as the caller, and a
7314        // filter that follows a relationship of its own then needed a
7315        // permission on *that* table: `dml_rbp_tasks` is filtered through
7316        // `grants`, which the role reading tasks may not read.
7317        let permission = permission_predicate(
7318            caller,
7319            child_scope.names,
7320            &plan.foreign_schema,
7321            &plan.foreign_table,
7322        )?;
7323        if let Some(predicate) = permission {
7324            if let Some(sql) =
7325                build_condition(&predicate, &child_scope, param_idx, values, alias_counter)?
7326            {
7327                correlation.push(format!("({})", sql));
7328            }
7329        }
7330    }
7331    if correlation.is_empty() {
7332        correlation.push("true".to_string());
7333    }
7334
7335    Ok(format!(
7336        "EXISTS (SELECT 1 FROM {} AS {} WHERE {})",
7337        source,
7338        postrust_sql::escape_ident(&alias),
7339        correlation.join(" AND ")
7340    ))
7341}
7342
7343/// `_exists`, the predicate only a permission can write, as SQL.
7344///
7345/// ```json
7346/// {"_exists": {"_table": "user", "_where": {"id": "X-Hasura-User-Id",
7347///                                           "is_admin": true}}}
7348/// ```
7349///
7350/// It asks whether a row exists in *another* table -- one no foreign key
7351/// relates to this one. That is how Hasura writes "the caller is an
7352/// administrator" without asking the caller to say so: the row that decides it
7353/// lives in a table of its own, and whether the account this predicate guards
7354/// is readable does not depend on which account it is.
7355///
7356/// So the subselect is **uncorrelated**, which is the whole difference from
7357/// the `EXISTS` a relationship predicate builds. There are no key columns to
7358/// join on -- if there were, the permission would have been written as a
7359/// relationship -- and the `_where` is read against the named table's columns
7360/// alone.
7361///
7362/// The caller's own permissions are not applied inside it, and that is not an
7363/// oversight. The point of `_exists` is to consult a table the role has no
7364/// access to: in Hasura's corpus the role that reads `account` through this
7365/// predicate may not read `public.user` at all. Narrowing the subselect to
7366/// what the caller may read would make the predicate refuse itself.
7367fn table_exists_sql(
7368    spec: &serde_json::Value,
7369    scope: &WhereScope<'_>,
7370    param_idx: &mut usize,
7371    values: &mut Vec<serde_json::Value>,
7372    alias_counter: &mut usize,
7373) -> Result<String, async_graphql::Error> {
7374    let Some((schema, table)) = crate::role::exists_target(spec, &scope.qualified.schema) else {
7375        return Err(async_graphql::Error::new(
7376            "\"_exists\" needs a \"_table\" to look in",
7377        ));
7378    };
7379
7380    // The name comes from a permission document rather than from a request,
7381    // but it still reaches SQL, and a table that is not there would reach it as
7382    // a syntax error at the far end rather than as an answer here.
7383    let qualified = postrust_core::api_request::QualifiedIdentifier::new(&schema, &table);
7384    if scope
7385        .resolution
7386        .as_ref()
7387        .is_some_and(|r| r.cache.get_table(&qualified).is_none())
7388    {
7389        return Err(async_graphql::Error::new(format!(
7390            "\"_exists\" names a table this server does not have: \"{}.{}\"",
7391            schema, table
7392        )));
7393    }
7394
7395    *alias_counter += 1;
7396    let alias = format!("pgrst_exists_{}", alias_counter);
7397    // The type this table is exposed as, which is the key into the
7398    // relationship map -- so a `_where` may follow the named table's own
7399    // relationships, the way any other predicate over it could.
7400    let type_name = scope
7401        .names
7402        .base_name(&schema, &table)
7403        .unwrap_or(&table)
7404        .to_string();
7405    let mut inner = WhereScope::aliased(&schema, &table, &alias, &type_name, scope);
7406    // See above: the predicate is the permission, so it is not itself
7407    // subject to one.
7408    inner.caller = None;
7409
7410    let condition = match spec.get("_where") {
7411        Some(predicate) => build_condition(predicate, &inner, param_idx, values, alias_counter)?,
7412        None => None,
7413    };
7414
7415    Ok(format!(
7416        "EXISTS (SELECT 1 FROM {}.{} AS {} WHERE {})",
7417        postrust_sql::escape_ident(&schema),
7418        postrust_sql::escape_ident(&table),
7419        postrust_sql::escape_ident(&alias),
7420        condition.unwrap_or_else(|| "true".to_string())
7421    ))
7422}
7423
7424/// A predicate over an aggregate of a related set, as SQL.
7425///
7426/// `{count: {predicate: {_gt: 2}, filter: {...}}}` becomes a scalar subselect
7427/// correlated back to the parent row, compared the way any column is:
7428///
7429/// ```text
7430/// (SELECT count(*) FROM "public"."article" AS pgrst_rel_1
7431///   WHERE "author"."id" = pgrst_rel_1."author_id" AND (...)) > $1
7432/// ```
7433///
7434/// A scalar subselect rather than an `EXISTS`, because the answer is a number
7435/// or a truth value and not whether a row is there -- and because over no rows
7436/// at all `count` is zero and `bool_and` is null, which is what SQL says and
7437/// what a client asking "no articles" is relying on.
7438fn aggregate_predicate_sql(
7439    relationship: &RelationshipField,
7440    expression: &serde_json::Value,
7441    scope: &WhereScope<'_>,
7442    param_idx: &mut usize,
7443    values: &mut Vec<serde_json::Value>,
7444    alias_counter: &mut usize,
7445) -> Result<String, async_graphql::Error> {
7446    let serde_json::Value::Object(over) = expression else {
7447        return Ok("true".to_string());
7448    };
7449
7450    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7451        async_graphql::Error::new(format!(
7452            "filtering on the relationship \"{}\" is not available here",
7453            relationship.name
7454        ))
7455    })?;
7456    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7457        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7458    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7459        return Err(async_graphql::Error::new(format!(
7460            "aggregating over \"{}\" is not supported: it is reached through a junction \
7461             rather than by a key",
7462            relationship.name
7463        )));
7464    }
7465
7466    let mut conditions: Vec<String> = Vec::new();
7467    for (function, spec) in over {
7468        if spec.is_null() {
7469            continue;
7470        }
7471        let serde_json::Value::Object(spec) = spec else {
7472            continue;
7473        };
7474        let Some(predicate) = spec.get("predicate").filter(|p| !p.is_null()) else {
7475            return Err(validation_error(format!(
7476                "\"{}\" over \"{}\" needs a predicate",
7477                function, relationship.name
7478            )));
7479        };
7480
7481        *alias_counter += 1;
7482        let alias = format!("pgrst_agg_{}", alias_counter);
7483        let child_scope = WhereScope::aliased(
7484            &plan.foreign_schema,
7485            &plan.foreign_table,
7486            &alias,
7487            &relationship.target_type,
7488            scope,
7489        );
7490
7491        // Correlated the way an `EXISTS` over the same relationship is: by
7492        // the key, or by handing the parent row to the function.
7493        let (source, mut correlation) = match &plan.function {
7494            Some(routine) => (
7495                format!(
7496                    "{}.{}({})",
7497                    postrust_sql::escape_ident(&routine.schema),
7498                    postrust_sql::escape_ident(&routine.name),
7499                    scope.row_ref
7500                ),
7501                Vec::new(),
7502            ),
7503            None => {
7504                let mut columns = Vec::with_capacity(plan.columns.len());
7505                for (parent_column, child_column) in &plan.columns {
7506                    columns.push(format!(
7507                        "{} = {}",
7508                        scope.column(parent_column),
7509                        child_scope.column(child_column)
7510                    ));
7511                }
7512                (
7513                    format!(
7514                        "{}.{}",
7515                        postrust_sql::escape_ident(&plan.foreign_schema),
7516                        postrust_sql::escape_ident(&plan.foreign_table)
7517                    ),
7518                    columns,
7519                )
7520            }
7521        };
7522        // `filter` narrows what is aggregated, and is an ordinary boolean
7523        // expression over the child.
7524        if let Some(filter) = spec.get("filter").filter(|f| !f.is_null()) {
7525            if let Some(sql) =
7526                build_condition(filter, &child_scope, param_idx, values, alias_counter)?
7527            {
7528                correlation.push(format!("({})", sql));
7529            }
7530        }
7531        if correlation.is_empty() {
7532            correlation.push("true".to_string());
7533        }
7534
7535        let arguments = spec.get("arguments").filter(|a| !a.is_null());
7536        let distinct = matches!(spec.get("distinct"), Some(serde_json::Value::Bool(true)));
7537        let (aggregate, operand_type) = match function.as_str() {
7538            "count" => {
7539                let columns: Vec<String> = match arguments {
7540                    Some(serde_json::Value::Array(items)) => items
7541                        .iter()
7542                        .filter_map(|item| item.as_str())
7543                        .map(|column| child_scope.column(column))
7544                        .collect(),
7545                    Some(serde_json::Value::String(one)) => vec![child_scope.column(one)],
7546                    _ => Vec::new(),
7547                };
7548                let counted = match columns.len() {
7549                    0 => "*".to_string(),
7550                    1 => columns.into_iter().next().unwrap_or_default(),
7551                    _ => format!("({})", columns.join(", ")),
7552                };
7553                let counted = match distinct && counted != "*" {
7554                    true => format!("DISTINCT {}", counted),
7555                    false => counted,
7556                };
7557                (format!("count({})", counted), "integer")
7558            }
7559            "bool_and" | "bool_or" => {
7560                let Some(column) = arguments.and_then(|a| a.as_str()) else {
7561                    return Err(validation_error(format!(
7562                        "\"{}\" over \"{}\" needs the column to fold",
7563                        function, relationship.name
7564                    )));
7565                };
7566                (
7567                    format!("{}({})", function, child_scope.column(column)),
7568                    "boolean",
7569                )
7570            }
7571            other => {
7572                return Err(validation_error(format!(
7573                    "\"{}\" is not an aggregate a predicate can be written over",
7574                    other
7575                )))
7576            }
7577        };
7578
7579        let subselect = format!(
7580            "(SELECT {} FROM {} AS {} WHERE {})",
7581            aggregate,
7582            source,
7583            postrust_sql::escape_ident(&alias),
7584            correlation.join(" AND ")
7585        );
7586        let serde_json::Value::Object(operators) = predicate else {
7587            return Err(validation_error(format!(
7588                "the predicate on \"{}\" is not a comparison",
7589                function
7590            )));
7591        };
7592        for (operator, operand) in operators {
7593            conditions.push(comparison_sql(
7594                &subselect,
7595                function,
7596                Some(operand_type),
7597                operator,
7598                operand,
7599                param_idx,
7600                values,
7601            )?);
7602        }
7603    }
7604
7605    Ok(match conditions.is_empty() {
7606        true => "true".to_string(),
7607        false => format!("({})", conditions.join(" AND ")),
7608    })
7609}
7610
7611/// One spatial comparison, as a PostGIS call.
7612///
7613/// The operand arrives as GeoJSON, which is what Hasura accepts and what a
7614/// client sends, so it is parsed by `ST_GeomFromGeoJSON` rather than bound as
7615/// a shape. The cast that follows is the column's own type: the same function
7616/// takes a geometry or a geography and picking the wrong one is not an
7617/// overload PostGIS has.
7618#[allow(clippy::too_many_arguments)]
7619fn postgis_sql(
7620    quoted: &str,
7621    column_type: Option<&str>,
7622    function: &str,
7623    op: &str,
7624    operand: &serde_json::Value,
7625    param_idx: &mut usize,
7626    values: &mut Vec<serde_json::Value>,
7627) -> Result<String, async_graphql::Error> {
7628    let is_geography = column_type == Some("geography");
7629    let mut shape = |value: &serde_json::Value, param_idx: &mut usize| -> String {
7630        let placeholder = format!("${}", param_idx);
7631        *param_idx += 1;
7632        values.push(value.clone());
7633        match is_geography {
7634            true => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
7635            false => format!("ST_GeomFromGeoJSON({})", placeholder),
7636        }
7637    };
7638
7639    match op {
7640        // `{distance, from}`: the shape and how far from it.
7641        "_st_d_within" | "_st_3d_d_within" => {
7642            let serde_json::Value::Object(spec) = operand else {
7643                return Err(async_graphql::Error::new(format!(
7644                    "\"{}\" takes {{distance, from}}",
7645                    op
7646                )));
7647            };
7648            let Some(from) = spec.get("from") else {
7649                return Err(async_graphql::Error::new(format!(
7650                    "\"{}\" needs a shape to measure from",
7651                    op
7652                )));
7653            };
7654            let Some(distance) = spec.get("distance") else {
7655                return Err(async_graphql::Error::new(format!(
7656                    "\"{}\" needs a distance",
7657                    op
7658                )));
7659            };
7660            let from_sql = shape(from, param_idx);
7661            let distance_sql = format!("${}", param_idx);
7662            *param_idx += 1;
7663            values.push(distance.clone());
7664            // On a sphere the answer depends on which sphere: `ST_DWithin`
7665            // over geography measures on the spheroid by default and on a
7666            // perfect sphere when told not to, and the two disagree by enough
7667            // to change which rows come back. Geometry has no such argument.
7668            let spheroid = match (is_geography, spec.get("use_spheroid")) {
7669                (true, Some(use_spheroid)) if !use_spheroid.is_null() => {
7670                    let placeholder = format!("${}", param_idx);
7671                    *param_idx += 1;
7672                    values.push(use_spheroid.clone());
7673                    format!(", {}::boolean", placeholder)
7674                }
7675                _ => String::new(),
7676            };
7677            Ok(format!(
7678                "{}({}, {}, {}::float8{})",
7679                function, quoted, from_sql, distance_sql, spheroid
7680            ))
7681        }
7682        // A raster against another raster, bound as one rather than parsed.
7683        "_st_intersects_rast" => {
7684            let placeholder = format!("${}", param_idx);
7685            *param_idx += 1;
7686            values.push(operand.clone());
7687            Ok(format!("{}({}, {}::raster)", function, quoted, placeholder))
7688        }
7689        // A raster against a shape, in one band or in any.
7690        "_st_intersects_geom_nband" | "_st_intersects_nband_geom" => {
7691            let serde_json::Value::Object(spec) = operand else {
7692                return Err(async_graphql::Error::new(format!(
7693                    "\"{}\" takes an object",
7694                    op
7695                )));
7696            };
7697            let Some(geometry) = spec.get("geommin") else {
7698                return Err(async_graphql::Error::new(format!(
7699                    "\"{}\" needs a shape",
7700                    op
7701                )));
7702            };
7703            let band = spec.get("nband").filter(|v| !v.is_null());
7704            let geometry_sql = shape(geometry, param_idx);
7705            Ok(match band {
7706                None => format!("{}({}, {})", function, quoted, geometry_sql),
7707                Some(band) => {
7708                    let placeholder = format!("${}", param_idx);
7709                    *param_idx += 1;
7710                    values.push(band.clone());
7711                    // `ST_Intersects(raster, nband, geometry)` is the spelling
7712                    // with a band; the argument order is PostGIS's, not the
7713                    // input's.
7714                    format!(
7715                        "{}({}, {}::int, {})",
7716                        function, quoted, placeholder, geometry_sql
7717                    )
7718                }
7719            })
7720        }
7721        // Everything else is the relation between this shape and one other.
7722        _ => {
7723            let other = shape(operand, param_idx);
7724            Ok(format!("{}({}, {})", function, quoted, other))
7725        }
7726    }
7727}
7728
7729/// A list of values as an array PostgreSQL will read.
7730///
7731/// Bound one element at a time rather than as one parameter. A JSON array
7732/// arrives as the text `["a","b"]`, which is not an array literal -- PostgreSQL
7733/// answers `malformed array literal` and means it.
7734fn sql_array(
7735    items: &[serde_json::Value],
7736    cast: &str,
7737    param_idx: &mut usize,
7738    values: &mut Vec<serde_json::Value>,
7739) -> String {
7740    if items.is_empty() {
7741        return format!("ARRAY[]::{}", cast);
7742    }
7743    let placeholders: Vec<String> = items
7744        .iter()
7745        .map(|item| {
7746            let placeholder = format!("${}", param_idx);
7747            *param_idx += 1;
7748            values.push(item.clone());
7749            placeholder
7750        })
7751        .collect();
7752    format!("ARRAY[{}]::{}", placeholders.join(", "), cast)
7753}
7754
7755/// One comparison against one column.
7756#[allow(clippy::too_many_arguments)]
7757fn comparison_sql(
7758    quoted: &str,
7759    column: &str,
7760    column_type: Option<&str>,
7761    op: &str,
7762    operand: &serde_json::Value,
7763    param_idx: &mut usize,
7764    values: &mut Vec<serde_json::Value>,
7765) -> Result<String, async_graphql::Error> {
7766    // `_cast` is not a comparison but a change of what is being compared: the
7767    // column becomes another type and the comparisons inside it apply to that.
7768    // A geometry and a geography answer different questions about the same
7769    // shape -- one on a plane, one on a sphere -- and this is how a client asks
7770    // for the other without the schema carrying two columns.
7771    if op == "_cast" {
7772        let serde_json::Value::Object(casts) = operand else {
7773            return Err(async_graphql::Error::new(
7774                "_cast takes an object naming the type to compare as",
7775            ));
7776        };
7777        let mut conditions = Vec::new();
7778        for (target, comparisons) in casts {
7779            // The GraphQL name of what is being compared as, and the
7780            // PostgreSQL type behind it. `String` is the one that differs:
7781            // a document compared as text is compared as `text`.
7782            let pg_type = match target.as_str() {
7783                "geometry" | "geography" => target.as_str(),
7784                "String" => "text",
7785                _ => {
7786                    return Err(async_graphql::Error::new(format!(
7787                        "cannot compare \"{}\" as \"{}\"",
7788                        column, target
7789                    )))
7790                }
7791            };
7792            let serde_json::Value::Object(ops) = comparisons else {
7793                continue;
7794            };
7795            let cast = format!("{}::{}", quoted, pg_type);
7796            for (nested_op, nested_operand) in ops {
7797                conditions.push(comparison_sql(
7798                    &cast,
7799                    column,
7800                    Some(pg_type),
7801                    nested_op,
7802                    nested_operand,
7803                    param_idx,
7804                    values,
7805                )?);
7806            }
7807        }
7808        return Ok(match conditions.len() {
7809            0 => "true".to_string(),
7810            1 => conditions.pop().expect("just counted"),
7811            _ => format!("({})", conditions.join(" AND ")),
7812        });
7813    }
7814
7815    // A spatial relation is a function of two shapes rather than an operator
7816    // between them, so it is written before the operator table is consulted.
7817    if let Some(function) = crate::input::bool_exp::postgis_function(op) {
7818        return postgis_sql(
7819            quoted,
7820            column_type,
7821            function,
7822            op,
7823            operand,
7824            param_idx,
7825            values,
7826        );
7827    }
7828
7829    // A tree comparison is an operator, but one whose operand has to be cast:
7830    // `?` is "any of these labels" for an ltree and "has this key" for a
7831    // jsonb, and PostgreSQL tells them apart by the operand's type alone.
7832    if let Some((operator, cast)) = crate::input::bool_exp::ltree_operator(op) {
7833        if cast.ends_with("[]") {
7834            let items = operand.as_array().ok_or_else(|| {
7835                async_graphql::Error::new(format!(
7836                    "the \"{}\" comparison on \"{}\" takes a list",
7837                    op, column
7838                ))
7839            })?;
7840            return Ok(format!(
7841                "{} {} {}",
7842                quoted,
7843                operator,
7844                sql_array(items, cast, param_idx, values)
7845            ));
7846        }
7847        let placeholder = format!("${}", param_idx);
7848        *param_idx += 1;
7849        values.push(operand.clone());
7850        return Ok(format!("{} {} {}::{}", quoted, operator, placeholder, cast));
7851    }
7852
7853    // The path language is a query over the document rather than a comparison
7854    // against one, so it is a function of the column and the path: `_exists`
7855    // asks whether the path selects anything, `_match` whether the predicate
7856    // it ends in holds.
7857    if let Some(function) = match op {
7858        "_jsonb_path_exists" => Some("jsonb_path_exists"),
7859        "_jsonb_path_match" => Some("jsonb_path_match"),
7860        _ => None,
7861    } {
7862        let placeholder = format!("${}", param_idx);
7863        *param_idx += 1;
7864        values.push(operand.clone());
7865        return Ok(format!(
7866            "{}({}, {}::jsonpath)",
7867            function, quoted, placeholder
7868        ));
7869    }
7870
7871    // Comparisons binding exactly one parameter, by the SQL they become.
7872    let binary = match op {
7873        "_eq" => Some("="),
7874        "_neq" => Some("<>"),
7875        "_gt" => Some(">"),
7876        "_gte" => Some(">="),
7877        "_lt" => Some("<"),
7878        "_lte" => Some("<="),
7879        "_like" => Some("LIKE"),
7880        "_nlike" => Some("NOT LIKE"),
7881        "_ilike" => Some("ILIKE"),
7882        "_nilike" => Some("NOT ILIKE"),
7883        "_similar" => Some("SIMILAR TO"),
7884        "_nsimilar" => Some("NOT SIMILAR TO"),
7885        "_regex" => Some("~"),
7886        "_nregex" => Some("!~"),
7887        "_iregex" => Some("~*"),
7888        "_niregex" => Some("!~*"),
7889        "_contains" => Some("@>"),
7890        "_contained_in" => Some("<@"),
7891        "_has_key" => Some("?"),
7892        _ => None,
7893    };
7894
7895    if let Some(sql_op) = binary {
7896        let placeholder = format!("${}", param_idx);
7897        *param_idx += 1;
7898        // A containment operand is a whole document, and `"latest"` is one --
7899        // a JSON string, which is not the same text as `latest`. Binding the
7900        // bare string answered `invalid input syntax for type json`, so what
7901        // goes over the wire is the value as it is written in JSON.
7902        values.push(match op {
7903            "_contains" | "_contained_in" => match operand {
7904                serde_json::Value::String(_)
7905                | serde_json::Value::Number(_)
7906                | serde_json::Value::Bool(_) => serde_json::Value::String(operand.to_string()),
7907                other => other.clone(),
7908            },
7909            _ => operand.clone(),
7910        });
7911        // A bound parameter arrives as text and PostgreSQL infers a type for
7912        // it from the operator, which works only while the inference is
7913        // unambiguous. `jsonb @> text` is not an operator at all; and `id =
7914        // $1` against an integer column answers `operator does not exist:
7915        // integer = text` the moment the value is one PostgreSQL cannot read
7916        // as a number -- a null, most obviously, which is how a client asks a
7917        // question it expects an empty answer to.
7918        //
7919        // So the operand says what it is. The containment and key operators
7920        // name the type the operator needs; everything else names the
7921        // column's, which is the type it is being compared against.
7922        let cast = match op {
7923            "_contains" | "_contained_in" => "::jsonb".to_string(),
7924            "_has_key" => "::text".to_string(),
7925            // Only where inference would otherwise pick text and be wrong. A
7926            // cast tells PostgreSQL what the *result* is, and it infers the
7927            // parameter feeding it as text -- so `$1::int4` bound with a
7928            // number sends binary int8 bytes into a text parameter and
7929            // answers `invalid byte sequence for encoding "UTF8"`. A value
7930            // PostgreSQL can read as a number needs no help; a null is the one
7931            // that does, because `id = $1` with nothing to infer from is
7932            // `integer = text`.
7933            _ if operand.is_null() => match column_type {
7934                Some(pg_type) => format!("::{}", pg_type),
7935                None => String::new(),
7936            },
7937            // A parameter arrives as `text`, and PostgreSQL then resolves the
7938            // operator by taking the *column* down to text where it can --
7939            // which is the wrong operator, and sometimes no operator at all.
7940            // `citext = text` compares case-sensitively against a
7941            // case-insensitive column; `uuid = text` does not exist. So a
7942            // written value names the type it is being compared against.
7943            //
7944            // Only a string: a number bound with a cast is sent as binary into
7945            // a parameter PostgreSQL has inferred as text, which is the
7946            // `invalid byte sequence for encoding "UTF8"` this used to answer.
7947            _ if operand.is_string() => match column_type {
7948                Some(pg_type) => format!("::{}", pg_type),
7949                None => String::new(),
7950            },
7951            _ => String::new(),
7952        };
7953        return Ok(format!("{} {} {}{}", quoted, sql_op, placeholder, cast));
7954    }
7955
7956    match op {
7957        "_is_null" => Ok(if operand.as_bool().unwrap_or(false) {
7958            format!("{} IS NULL", quoted)
7959        } else {
7960            format!("{} IS NOT NULL", quoted)
7961        }),
7962        "_in" | "_nin" => {
7963            let items = operand.as_array().ok_or_else(|| {
7964                async_graphql::Error::new(format!(
7965                    "the \"{}\" comparison on \"{}\" requires a list of values",
7966                    op, column
7967                ))
7968            })?;
7969            if items.is_empty() {
7970                // `IN ()` is not valid SQL. An empty `_in` matches nothing and
7971                // an empty `_nin` matches everything.
7972                return Ok(if op == "_in" { "false" } else { "true" }.to_string());
7973            }
7974            let mut placeholders = Vec::with_capacity(items.len());
7975            for item in items {
7976                // Cast only a null, for the reason the binary comparisons
7977                // give: a cast makes PostgreSQL infer the parameter as text.
7978                let cast = match (item.is_null() || item.is_string(), column_type) {
7979                    (true, Some(pg_type)) => format!("::{}", pg_type),
7980                    _ => String::new(),
7981                };
7982                placeholders.push(format!("${}{}", param_idx, cast));
7983                values.push(item.clone());
7984                *param_idx += 1;
7985            }
7986            Ok(format!(
7987                "{} {} ({})",
7988                quoted,
7989                if op == "_in" { "IN" } else { "NOT IN" },
7990                placeholders.join(", ")
7991            ))
7992        }
7993        "_has_keys_any" | "_has_keys_all" => {
7994            let items = operand.as_array().ok_or_else(|| {
7995                async_graphql::Error::new(format!(
7996                    "the \"{}\" comparison on \"{}\" requires a list of keys",
7997                    op, column
7998                ))
7999            })?;
8000            Ok(format!(
8001                "{} {} {}",
8002                quoted,
8003                if op == "_has_keys_any" { "?|" } else { "?&" },
8004                sql_array(items, "text[]", param_idx, values)
8005            ))
8006        }
8007        other => Err(async_graphql::Error::new(format!(
8008            "unsupported comparison \"{}\" on \"{}\"",
8009            other, column
8010        ))),
8011    }
8012}
8013
8014/// Build an `ORDER BY` clause from the `order_by` argument.
8015///
8016/// The argument is a list of single-column objects -- `[{name: asc}, {id:
8017/// desc}]` -- and the list is ordered, which is why it is a list and not one
8018/// object with several keys. Column names are checked against the table in the
8019/// schema cache before being quoted, so a name that is unknown, or crafted to
8020/// inject SQL, is rejected rather than interpolated. Returns an empty string
8021/// when no ordering was requested.
8022#[allow(clippy::too_many_arguments)] // one parameter per SQL clause
8023async fn build_order_by_clause(
8024    ctx: &ResolverContext<'_>,
8025    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8026    schema_name: &str,
8027    table_name: &str,
8028    type_name: &str,
8029    relationships: &HashMap<String, Vec<RelationshipField>>,
8030    reference: &str,
8031    names: &crate::names::NameOverrides,
8032) -> Result<String, async_graphql::Error> {
8033    let Ok(order_arg) = ctx.args.try_get("order_by") else {
8034        return Ok(String::new());
8035    };
8036
8037    let value = accessor_to_json(&order_arg);
8038    // A single object is accepted as a one-entry list, which is what a client
8039    // writing `order_by: {name: asc}` means and what Hasura reads it as.
8040    let entries: Vec<&serde_json::Value> = match &value {
8041        serde_json::Value::Array(items) => items.iter().collect(),
8042        serde_json::Value::Object(_) => vec![&value],
8043        serde_json::Value::Null => return Ok(String::new()),
8044        _ => {
8045            return Err(async_graphql::Error::new(
8046                "order_by takes an object or a list of them",
8047            ))
8048        }
8049    };
8050    if entries.is_empty() {
8051        return Ok(String::new());
8052    }
8053
8054    let guard = schema_cache
8055        .get()
8056        .await
8057        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8058    let cache = guard
8059        .as_ref()
8060        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8061    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8062    let table = cache
8063        .get_table(&qi)
8064        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8065
8066    let mut terms = Vec::new();
8067    let mut alias_counter = 0usize;
8068    for entry in entries {
8069        order_terms_into(
8070            entry,
8071            cache,
8072            relationships,
8073            type_name,
8074            table,
8075            reference,
8076            names,
8077            &mut alias_counter,
8078            &mut terms,
8079        )?;
8080    }
8081
8082    if terms.is_empty() {
8083        return Ok(String::new());
8084    }
8085    Ok(format!(" ORDER BY {}", terms.join(", ")))
8086}
8087
8088/// Collect the `ORDER BY` terms one entry contributes.
8089///
8090/// A key whose value is a direction is a column of this table. A key whose
8091/// value is an object is something the row points at, and ordering by it is a
8092/// correlated subselect: one related row contributes its column, and many
8093/// contribute an aggregate. That is the whole difference between the two
8094/// sides, and PostgreSQL will take a scalar subquery anywhere a column goes.
8095#[allow(clippy::too_many_arguments)]
8096fn order_terms_into(
8097    entry: &serde_json::Value,
8098    cache: &SchemaCache,
8099    relationships: &HashMap<String, Vec<RelationshipField>>,
8100    type_name: &str,
8101    table: &postrust_core::schema_cache::Table,
8102    reference: &str,
8103    names: &crate::names::NameOverrides,
8104    alias_counter: &mut usize,
8105    terms: &mut Vec<String>,
8106) -> Result<(), async_graphql::Error> {
8107    let serde_json::Value::Object(map) = entry else {
8108        return Err(async_graphql::Error::new(
8109            "each order_by entry is an object mapping a column to a direction",
8110        ));
8111    };
8112    let available: &[RelationshipField] = relationships
8113        .get(type_name)
8114        .map(|r| r.as_slice())
8115        .unwrap_or(&[]);
8116
8117    for (key, value) in map {
8118        // A direction given as null is no direction. `order_by: {id:
8119        // $direction}` with nothing bound for `$direction` is how a client
8120        // makes an ordering optional without writing the query twice, and the
8121        // same goes for `{author: $author_order_by}`, which is a whole
8122        // ordering left out.
8123        if value.is_null() {
8124            continue;
8125        }
8126
8127        // A direction: this table's own column, or a function of its row.
8128        if let Some(name) = value.as_str() {
8129            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8130                async_graphql::Error::new(format!(
8131                    "\"{}\" is not a sort direction; expected one of asc, desc, \
8132                     asc_nulls_first, asc_nulls_last, desc_nulls_first, desc_nulls_last",
8133                    name
8134                ))
8135            })?;
8136            let column = table_column_for(names, table, key);
8137            if table.get_column(column).is_some() {
8138                terms.push(format!(
8139                    "{}.{} {}",
8140                    reference,
8141                    postrust_sql::escape_ident(column),
8142                    sql
8143                ));
8144                continue;
8145            }
8146            // A computed field is a column as far as ordering is concerned:
8147            // the row is the argument, and the call is what is sorted on.
8148            let function = names
8149                .computed_source(&table.schema, &table.name, key)
8150                .unwrap_or(key);
8151            if let Some(definition) = table.get_computed_column(function) {
8152                terms.push(format!(
8153                    "{} {}",
8154                    computed_call(definition, &format!("{}.*", reference), reference),
8155                    sql
8156                ));
8157                continue;
8158            }
8159            return Err(async_graphql::Error::new(format!(
8160                "cannot order by unknown column \"{}\" on \"{}\"",
8161                key, table.name
8162            )));
8163        }
8164
8165        // An aggregate of the rows that point here.
8166        if let Some(rel) = key
8167            .strip_suffix("_aggregate")
8168            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list))
8169        {
8170            aggregate_order_terms(value, rel, cache, reference, alias_counter, terms)?;
8171            continue;
8172        }
8173
8174        // A column of the row this one points at.
8175        if let Some(rel) = available.iter().find(|r| r.name == *key && !r.is_list) {
8176            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8177                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8178            if plan.columns.is_empty() {
8179                return Err(async_graphql::Error::new(format!(
8180                    "cannot order by \"{}\": it is not reached by a key",
8181                    key
8182                )));
8183            }
8184            *alias_counter += 1;
8185            let alias = format!("pgrst_ord_{}", alias_counter);
8186            let quoted_alias = postrust_sql::escape_ident(&alias);
8187            let correlation = plan
8188                .columns
8189                .iter()
8190                .map(|(local, foreign)| {
8191                    format!(
8192                        "{} = {}.{}",
8193                        format_args!("{}.{}", reference, postrust_sql::escape_ident(local)),
8194                        quoted_alias,
8195                        postrust_sql::escape_ident(foreign)
8196                    )
8197                })
8198                .collect::<Vec<_>>()
8199                .join(" AND ");
8200
8201            let target_qi = postrust_core::api_request::QualifiedIdentifier::new(
8202                &plan.foreign_schema,
8203                &plan.foreign_table,
8204            );
8205            let Some(target) = cache.get_table(&target_qi) else {
8206                return Err(async_graphql::Error::new(format!(
8207                    "unknown table \"{}\"",
8208                    plan.foreign_table
8209                )));
8210            };
8211
8212            // The related row's own terms, then wrapped one at a time: a
8213            // subquery yields one value, so each term is its own subselect.
8214            let mut nested = Vec::new();
8215            order_terms_into(
8216                value,
8217                cache,
8218                relationships,
8219                &rel.target_type,
8220                target,
8221                &quoted_alias,
8222                names,
8223                alias_counter,
8224                &mut nested,
8225            )?;
8226            for term in nested {
8227                let (expression, direction) = term
8228                    .rsplit_once(' ')
8229                    .map(|(e, d)| (e.to_string(), d.to_string()))
8230                    .unwrap_or((term.clone(), String::new()));
8231                terms.push(format!(
8232                    "(SELECT {} FROM {}.{} AS {} WHERE {}) {}",
8233                    expression,
8234                    postrust_sql::escape_ident(&plan.foreign_schema),
8235                    postrust_sql::escape_ident(&plan.foreign_table),
8236                    quoted_alias,
8237                    correlation,
8238                    direction
8239                ));
8240            }
8241            continue;
8242        }
8243
8244        return Err(async_graphql::Error::new(format!(
8245            "cannot order by \"{}\" on \"{}\"",
8246            key, table.name
8247        )));
8248    }
8249    Ok(())
8250}
8251
8252/// The `ORDER BY` terms for an aggregate of a row's children.
8253fn aggregate_order_terms(
8254    value: &serde_json::Value,
8255    rel: &RelationshipField,
8256    cache: &SchemaCache,
8257    reference: &str,
8258    alias_counter: &mut usize,
8259    terms: &mut Vec<String>,
8260) -> Result<(), async_graphql::Error> {
8261    let serde_json::Value::Object(spec) = value else {
8262        return Err(async_graphql::Error::new(
8263            "ordering by an aggregate takes an object, such as {count: desc}",
8264        ));
8265    };
8266    let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8267        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8268    if plan.columns.is_empty() && plan.function.is_none() {
8269        return Err(async_graphql::Error::new(format!(
8270            "cannot order by an aggregate of \"{}\": it is not reached by a key",
8271            rel.name
8272        )));
8273    }
8274    // A computed relationship whose function takes more than the row cannot be
8275    // ordered by here: there is nowhere in `order_by` to write the arguments.
8276    if plan.function.is_some() && !rel.arguments.is_empty() {
8277        return Err(async_graphql::Error::new(format!(
8278            "cannot order by an aggregate of \"{}\": it takes arguments, and an \
8279             ordering has nowhere to write them",
8280            rel.name
8281        )));
8282    }
8283
8284    *alias_counter += 1;
8285    let alias = postrust_sql::escape_ident(&format!("pgrst_ord_{}", alias_counter));
8286    // A computed relationship is correlated by argument -- the function takes
8287    // the parent row -- where a key relationship is correlated by columns.
8288    let (source, correlation) = match &plan.function {
8289        Some(function) => {
8290            let session = plan.row_argument.as_ref().and_then(|row_argument| {
8291                computed_session_argument(cache, function, row_argument)
8292                    .map(|session| (row_argument.clone(), session))
8293            });
8294            let call = match session {
8295                Some((row_argument, session)) => format!(
8296                    "{} => {}, {} => {}",
8297                    postrust_sql::escape_ident(&row_argument),
8298                    reference,
8299                    postrust_sql::escape_ident(&session),
8300                    SESSION_ARGUMENT
8301                ),
8302                None => reference.to_string(),
8303            };
8304            (
8305                format!(
8306                    "{}.{}({})",
8307                    postrust_sql::escape_ident(&function.schema),
8308                    postrust_sql::escape_ident(&function.name),
8309                    call
8310                ),
8311                "true".to_string(),
8312            )
8313        }
8314        None => (
8315            format!(
8316                "{}.{}",
8317                postrust_sql::escape_ident(&plan.foreign_schema),
8318                postrust_sql::escape_ident(&plan.foreign_table)
8319            ),
8320            plan.columns
8321                .iter()
8322                .map(|(local, foreign)| {
8323                    format!(
8324                        "{}.{} = {}.{}",
8325                        reference,
8326                        postrust_sql::escape_ident(local),
8327                        alias,
8328                        postrust_sql::escape_ident(foreign)
8329                    )
8330                })
8331                .collect::<Vec<_>>()
8332                .join(" AND "),
8333        ),
8334    };
8335
8336    // `{count: desc}` is one term; `{max: {id: desc}}` is one per column.
8337    let mut wanted: Vec<(String, String)> = Vec::new();
8338    for (function, argument) in spec {
8339        // A direction given as null is no direction, as everywhere else an
8340        // ordering is read.
8341        if argument.is_null() {
8342            continue;
8343        }
8344        if function == "count" {
8345            let name = argument.as_str().unwrap_or_default();
8346            let Some(direction) = crate::input::order_by::direction_sql(name) else {
8347                return Err(async_graphql::Error::new(format!(
8348                    "\"{}\" is not a sort direction",
8349                    name
8350                )));
8351            };
8352            wanted.push(("count(*)".to_string(), direction.to_string()));
8353            continue;
8354        }
8355        let serde_json::Value::Object(columns) = argument else {
8356            continue;
8357        };
8358        for (column, direction) in columns {
8359            if direction.is_null() {
8360                continue;
8361            }
8362            let name = direction.as_str().unwrap_or_default();
8363            let Some(direction) = crate::input::order_by::direction_sql(name) else {
8364                return Err(async_graphql::Error::new(format!(
8365                    "\"{}\" is not a sort direction",
8366                    name
8367                )));
8368            };
8369            // The function comes from the generated input, not from the
8370            // request: a client can only name one that was offered.
8371            wanted.push((
8372                format!(
8373                    "{}({}.{})",
8374                    function,
8375                    alias,
8376                    postrust_sql::escape_ident(column)
8377                ),
8378                direction.to_string(),
8379            ));
8380        }
8381    }
8382
8383    for (expression, direction) in wanted {
8384        terms.push(format!(
8385            "(SELECT {} FROM {} AS {} WHERE {}) {}",
8386            expression, source, alias, correlation, direction
8387        ));
8388    }
8389    Ok(())
8390}
8391
8392/// The `DISTINCT ON (...)` prefix, and the ordering that has to go with it.
8393///
8394/// PostgreSQL keeps the first row of each DISTINCT ON group in the query's own
8395/// order, and picks arbitrarily where the ordering does not begin with the
8396/// distinct columns -- so which row survives would depend on the plan. Hasura
8397/// refuses that query rather than answering it, and so does this: prepending
8398/// the distinct columns instead produced an answer, and a wrong one, since
8399/// `ORDER BY "department", "department" DESC` is decided by its first term and
8400/// sorts ascending. Where nothing was ordered at all there is nothing to
8401/// disagree with, so the distinct columns become the ordering.
8402///
8403/// `written` is the ordering as its comma-separated terms, without the
8404/// keyword; the answer is in the same spelling.
8405fn distinct_on_clause(
8406    distinct_on: &[String],
8407    written: Option<&str>,
8408) -> Result<(String, Option<String>), async_graphql::Error> {
8409    if distinct_on.is_empty() {
8410        return Ok((String::new(), written.map(str::to_string)));
8411    }
8412    let terms: Vec<&str> = written
8413        .map(|written| written.split(", ").collect())
8414        .unwrap_or_default();
8415    // Terms are qualified and the distinct columns may not be, so they are
8416    // compared by the name itself.
8417    let column_of = |term: &str| {
8418        let expression = term.split_whitespace().next().unwrap_or(term);
8419        expression
8420            .rsplit_once('.')
8421            .map(|(_, name)| name)
8422            .unwrap_or(expression)
8423            .to_string()
8424    };
8425    let leading: Vec<String> = terms
8426        .iter()
8427        .take(distinct_on.len())
8428        .map(|term| column_of(term))
8429        .collect();
8430    if !terms.is_empty()
8431        && (leading.len() != distinct_on.len()
8432            || distinct_on
8433                .iter()
8434                .any(|column| !leading.contains(&column_of(column))))
8435    {
8436        return Err(validation_error(
8437            "\"distinct_on\" columns must match initial \"order_by\" columns",
8438        ));
8439    }
8440    let order = match terms.is_empty() {
8441        true => Some(distinct_on.join(", ")),
8442        false => written.map(str::to_string),
8443    };
8444    Ok((format!("DISTINCT ON ({}) ", distinct_on.join(", ")), order))
8445}
8446
8447/// Build the `DISTINCT ON (...)` prefix from the `distinct_on` argument.
8448///
8449/// PostgreSQL requires the leftmost `ORDER BY` terms to match the distinct
8450/// columns, and picks an arbitrary surviving row otherwise. The caller
8451/// prepends these terms to whatever ordering was asked for rather than
8452/// replacing it, so `distinct_on: [name]` with `order_by: [{id: desc}]` keeps
8453/// the highest id per name instead of an unpredictable one.
8454async fn build_distinct_on(
8455    ctx: &ResolverContext<'_>,
8456    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8457    schema_name: &str,
8458    table_name: &str,
8459    names: &crate::names::NameOverrides,
8460) -> Result<Vec<String>, async_graphql::Error> {
8461    let Ok(arg) = ctx.args.try_get("distinct_on") else {
8462        return Ok(Vec::new());
8463    };
8464    let value = accessor_to_json(&arg);
8465    let requested: Vec<String> = match &value {
8466        serde_json::Value::Array(items) => items
8467            .iter()
8468            .filter_map(|i| i.as_str().map(|s| s.to_string()))
8469            .collect(),
8470        serde_json::Value::String(one) => vec![one.clone()],
8471        _ => Vec::new(),
8472    };
8473    if requested.is_empty() {
8474        return Ok(Vec::new());
8475    }
8476
8477    let guard = schema_cache
8478        .get()
8479        .await
8480        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8481    let cache = guard
8482        .as_ref()
8483        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8484    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8485    let table = cache
8486        .get_table(&qi)
8487        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8488
8489    let mut quoted = Vec::with_capacity(requested.len());
8490    for field in requested {
8491        let name = table_column_for(names, table, &field).to_string();
8492        if table.get_column(&name).is_none() {
8493            return Err(async_graphql::Error::new(format!(
8494                "cannot take distinct on unknown column \"{}\" of \"{}\"",
8495                field, table_name
8496            )));
8497        }
8498        quoted.push(postrust_sql::escape_ident(&name));
8499    }
8500    Ok(quoted)
8501}
8502
8503/// Build the SELECT-list expressions that embed relationships in one query.
8504///
8505/// The GraphQL mirror of the REST builder: each requested relationship becomes a
8506/// correlated subselect yielding JSON, so the whole selection comes back from
8507/// the parent query instead of one query per relationship per level.
8508/// The SELECT-list entries for any computed columns the selection asks for.
8509///
8510/// A computed column is a function of one argument of the table's own row
8511/// type, so it is not in `*` and has to be named. PostgreSQL's functional
8512/// notation reads `upper_name(author.*)` and `author.upper_name` as the same
8513/// call; the explicit form is written here because it says which function is
8514/// being called.
8515#[allow(clippy::too_many_arguments)] // the fields, their source, and the binding state
8516fn computed_projections<'a>(
8517    table: &postrust_core::schema_cache::Table,
8518    fields: impl IntoIterator<Item = SelectionField<'a>>,
8519    row_reference: &str,
8520    names: &crate::names::NameOverrides,
8521    schema_cache: &SchemaCache,
8522    param_idx: &mut usize,
8523    values: &mut Vec<serde_json::Value>,
8524) -> Result<Vec<String>, async_graphql::Error> {
8525    let mut projections = Vec::new();
8526    for field in fields {
8527        let name = field.name();
8528        // A real column wins, and the projection already carries it -- under
8529        // this name, whether or not that is the column's own.
8530        if table
8531            .get_column(table_column_for(names, table, name))
8532            .is_some()
8533        {
8534            continue;
8535        }
8536        // The field may be exposed under a name that was given rather than
8537        // the function's own, so the call is looked up from either side.
8538        let function = names
8539            .computed_source(&table.schema, &table.name, name)
8540            .unwrap_or(name);
8541        let Some(computed) = table.get_computed_column(function) else {
8542            continue;
8543        };
8544        let arguments = crate::schema::computed_caller_arguments(computed, schema_cache);
8545        let given = match arguments.is_empty() {
8546            true => None,
8547            false => embed_arguments(field).remove("args"),
8548        };
8549        projections.push(format!(
8550            "{} AS {}",
8551            computed_column_call(
8552                computed,
8553                row_reference,
8554                name,
8555                &arguments,
8556                given.as_ref(),
8557                param_idx,
8558                values,
8559            )?,
8560            postrust_sql::escape_ident(name)
8561        ));
8562    }
8563    Ok(projections)
8564}
8565
8566/// The call behind a computed field, including whatever the caller passed.
8567///
8568/// The ordinary case is a function of the row alone, which is written
8569/// positionally: that is the call REST writes, and the one a client that never
8570/// heard of `args` is asking for. Anything else has to be written by name --
8571/// the row is then not the only parameter and may not be the first -- so the
8572/// row, the session where the function asks for one, and each argument the
8573/// caller gave are all named.
8574fn computed_column_call(
8575    computed: &postrust_core::schema_cache::ComputedColumn,
8576    row: &str,
8577    field_name: &str,
8578    arguments: &[(String, String, bool)],
8579    given: Option<&serde_json::Value>,
8580    param_idx: &mut usize,
8581    values: &mut Vec<serde_json::Value>,
8582) -> Result<String, async_graphql::Error> {
8583    if arguments.is_empty() {
8584        return Ok(computed_call(computed, row, row));
8585    }
8586    let Some(row_argument) = &computed.row_argument else {
8587        return Ok(computed_call(computed, row, row));
8588    };
8589    let function = format!(
8590        "{}.{}",
8591        postrust_sql::escape_ident(&computed.function.schema),
8592        postrust_sql::escape_ident(&computed.function.name)
8593    );
8594    let mut passed = vec![format!(
8595        "{} => {}",
8596        postrust_sql::escape_ident(row_argument),
8597        row
8598    )];
8599    if let Some(session) = &computed.session_argument {
8600        passed.push(format!(
8601            "{} => {}",
8602            postrust_sql::escape_ident(session),
8603            SESSION_ARGUMENT
8604        ));
8605    }
8606    for (name, pg_type, required) in arguments {
8607        match given.and_then(|given| given.get(name)) {
8608            None if *required => {
8609                return Err(async_graphql::Error::new(format!(
8610                    "{} needs the argument \"{}\"",
8611                    field_name, name
8612                )))
8613            }
8614            // Left out is left out, so the function's own default applies.
8615            None => continue,
8616            Some(value) => {
8617                passed.push(format!(
8618                    "{} => ${}::{}",
8619                    postrust_sql::escape_ident(name),
8620                    param_idx,
8621                    pg_type
8622                ));
8623                *param_idx += 1;
8624                values.push(value.clone());
8625            }
8626        }
8627    }
8628    Ok(format!("{}({})", function, passed.join(", ")))
8629}
8630
8631/// The `count(...)` a selection asked for.
8632///
8633/// `count` alone is `count(*)`, which counts rows. `count(columns: [a])` is
8634/// `count("a")`, which counts the rows where `a` is not null -- a different
8635/// number, and the one the client asked for. `distinct: true` counts the
8636/// distinct values among them, and several columns are counted as the row
8637/// they make, which is how PostgreSQL counts more than one thing at once.
8638///
8639/// The column names come from the generated enum, so a selection can only
8640/// name a column the table has; what it may not know is the column's own name
8641/// where the schema exposes it under another.
8642fn count_expression(
8643    field: async_graphql::SelectionField<'_>,
8644    table: (&str, &str),
8645    alias: Option<&str>,
8646    names: &crate::names::NameOverrides,
8647) -> String {
8648    let arguments = embed_arguments(field);
8649    let requested = match arguments.get("columns") {
8650        Some(serde_json::Value::Array(items)) => items
8651            .iter()
8652            .filter_map(|item| item.as_str())
8653            .collect::<Vec<_>>(),
8654        Some(serde_json::Value::String(one)) => vec![one.as_str()],
8655        _ => Vec::new(),
8656    };
8657    if requested.is_empty() {
8658        return "count(*)".to_string();
8659    }
8660    let columns: Vec<String> = requested
8661        .into_iter()
8662        .map(|column| {
8663            let (schema, name) = table;
8664            let source = column_for(names, schema, name, column);
8665            match alias {
8666                Some(alias) => format!(
8667                    "{}.{}",
8668                    postrust_sql::escape_ident(alias),
8669                    postrust_sql::escape_ident(source)
8670                ),
8671                None => postrust_sql::escape_ident(source),
8672            }
8673        })
8674        .collect();
8675    // One column counts itself; several count the row they make, since
8676    // `count(a, b)` is not a call PostgreSQL has.
8677    let counted = match columns.len() {
8678        1 => columns.into_iter().next().unwrap_or_default(),
8679        _ => format!("({})", columns.join(", ")),
8680    };
8681    let distinct = matches!(
8682        arguments.get("distinct"),
8683        Some(serde_json::Value::Bool(true))
8684    );
8685    match distinct {
8686        true => format!("count(DISTINCT {})", counted),
8687        false => format!("count({})", counted),
8688    }
8689}
8690
8691/// The SELECT list for a nested aggregate.
8692///
8693/// `articles_aggregate { aggregate { count } nodes { title } }` becomes one
8694/// row per parent, correlated the way any embed is. Both halves are aggregates
8695/// over the same correlated set, so they go in one select list rather than two
8696/// queries -- `count(*)` and `json_agg(...)` read the same rows.
8697#[allow(clippy::too_many_arguments)] // the recursion carries its whole context
8698fn nested_aggregate_select(
8699    caller: &crate::role::Caller<'_>,
8700    selection: async_graphql::SelectionField<'_>,
8701    child_alias: &str,
8702    schema_cache: &SchemaCache,
8703    relationships: &HashMap<String, Vec<RelationshipField>>,
8704    target_type: &str,
8705    child_table: Option<&postrust_core::schema_cache::Table>,
8706    max_rows: Option<i64>,
8707    alias_counter: &mut usize,
8708    param_idx: &mut usize,
8709    values: &mut Vec<serde_json::Value>,
8710    names: &crate::names::NameOverrides,
8711) -> Result<String, async_graphql::Error> {
8712    use crate::schema::aggregate as agg;
8713
8714    let mut parts: Vec<String> = Vec::new();
8715
8716    for child in selection.selection_set() {
8717        match child.name() {
8718            "aggregate" => {
8719                let of_table = child_table
8720                    .map(|table| (table.schema.as_str(), table.name.as_str()))
8721                    .unwrap_or(("", ""));
8722                // Each `count` is answered under the name it was asked for,
8723                // since two of them may count different things. Every other
8724                // aggregate is one object read under however many names, so
8725                // its columns are the union of what all of them named.
8726                let mut counts: Vec<(String, String)> = Vec::new();
8727                let mut wanted: BTreeMap<&str, Vec<String>> = BTreeMap::new();
8728                for function in child.selection_set() {
8729                    if function.name() == "count" {
8730                        counts.push((
8731                            function.alias().unwrap_or("count").to_string(),
8732                            count_expression(function, of_table, Some(child_alias), names),
8733                        ));
8734                        continue;
8735                    }
8736                    let sql_function = agg::NUMERIC_AGGREGATES
8737                        .iter()
8738                        .chain(agg::ORDERED_AGGREGATES.iter())
8739                        .find(|(name, _)| *name == function.name())
8740                        .map(|(name, _)| *name);
8741                    let Some(sql_function) = sql_function else {
8742                        continue;
8743                    };
8744                    let columns = wanted.entry(sql_function).or_default();
8745                    for column in function.selection_set() {
8746                        let name = column.name().to_string();
8747                        // Answered by the schema, not a column.
8748                        if name != "__typename" && !columns.contains(&name) {
8749                            columns.push(name);
8750                        }
8751                    }
8752                }
8753                // Answered whether or not it was asked for, for the reason
8754                // the root aggregate answers it.
8755                let mut fields = match counts.iter().any(|(key, _)| key == "count") {
8756                    true => Vec::new(),
8757                    false => vec!["'count', count(*)".to_string()],
8758                };
8759                for (key, expression) in &counts {
8760                    fields.push(format!(
8761                        "{}, {}",
8762                        postrust_sql::quote_literal(key),
8763                        expression
8764                    ));
8765                }
8766                for (sql_function, named) in &wanted {
8767                    let columns: Vec<String> = named
8768                        .iter()
8769                        .map(|column| {
8770                            let source = child_table
8771                                .map(|table| table_column_for(names, table, column))
8772                                .unwrap_or(column.as_str());
8773                            format!(
8774                                "{}, {}({}.{})",
8775                                postrust_sql::quote_literal(column),
8776                                sql_function,
8777                                postrust_sql::escape_ident(child_alias),
8778                                postrust_sql::escape_ident(source)
8779                            )
8780                        })
8781                        .collect();
8782                    // Pushed even when empty, as at the root: `max { __typename }`
8783                    // is an object with nothing in it, not a null.
8784                    fields.push(format!(
8785                        "'{}', json_build_object({})",
8786                        sql_function,
8787                        columns.join(", ")
8788                    ));
8789                }
8790                parts.push(format!(
8791                    "json_build_object({}) AS {}",
8792                    fields.join(", "),
8793                    postrust_sql::escape_ident("aggregate")
8794                ));
8795            }
8796            // `nodes` under an aggregate is a rows selection like any other:
8797            // what it names may be a column, a computed field, or a
8798            // relationship of its own, and the last of those is a further
8799            // correlated subselect rather than a column of this one.
8800            "nodes" => {
8801                let embeds = build_embed_expressions(
8802                    caller,
8803                    schema_cache,
8804                    relationships,
8805                    target_type,
8806                    child_alias,
8807                    child.selection_set(),
8808                    max_rows,
8809                    alias_counter,
8810                    param_idx,
8811                    values,
8812                    names,
8813                )?;
8814                let mut columns: Vec<String> = Vec::new();
8815                for column in child.selection_set() {
8816                    let name = column.name();
8817                    if name == "__typename" || embeds.iter().any(|(field, _)| field == name) {
8818                        continue;
8819                    }
8820                    columns.push(format!(
8821                        "{}, {}",
8822                        postrust_sql::quote_literal(name),
8823                        column_expression(child_table, child_alias, name, names)
8824                    ));
8825                }
8826                for (field, expression) in &embeds {
8827                    columns.push(format!(
8828                        "{}, {}",
8829                        postrust_sql::quote_literal(field),
8830                        expression
8831                    ));
8832                }
8833                let row = if columns.is_empty() {
8834                    format!("row_to_json({})", postrust_sql::escape_ident(child_alias))
8835                } else {
8836                    format!("json_build_object({})", columns.join(", "))
8837                };
8838                parts.push(format!(
8839                    "COALESCE(json_agg({}), '[]'::json) AS {}",
8840                    row,
8841                    postrust_sql::escape_ident("nodes")
8842                ));
8843            }
8844            _ => {}
8845        }
8846    }
8847
8848    if parts.is_empty() {
8849        // A selection of nothing but `__typename`. One row, no columns, is not
8850        // valid SQL; a count nobody reads is.
8851        parts.push("count(*) AS pgrst_empty".to_string());
8852    }
8853    Ok(parts.join(", "))
8854}
8855
8856/// How one named field of a row is read, given the alias the row goes by.
8857///
8858/// A column is itself; a computed field is the call that produces it. Which of
8859/// the two a name is depends on the table, so this is the one place that asks.
8860fn column_expression(
8861    table: Option<&postrust_core::schema_cache::Table>,
8862    alias: &str,
8863    name: &str,
8864    names: &crate::names::NameOverrides,
8865) -> String {
8866    let column = table
8867        .map(|t| table_column_for(names, t, name))
8868        .unwrap_or(name);
8869    let plain = format!(
8870        "{}.{}",
8871        postrust_sql::escape_ident(alias),
8872        postrust_sql::escape_ident(column)
8873    );
8874    let computed = table
8875        .filter(|t| t.get_column(column).is_none())
8876        .and_then(|t| {
8877            let function = names
8878                .computed_source(&t.schema, &t.name, name)
8879                .unwrap_or(name);
8880            t.get_computed_column(function)
8881        });
8882    match computed {
8883        Some(definition) => computed_call(
8884            definition,
8885            &format!("{}.*", postrust_sql::escape_ident(alias)),
8886            &postrust_sql::escape_ident(alias),
8887        ),
8888        None => plain,
8889    }
8890}
8891
8892/// Render `order_by` terms against a table, qualified with an alias.
8893///
8894/// The root field's ordering reads its argument from the resolver context and
8895/// needs the schema cache asynchronously; an embed already holds both, so this
8896/// takes the value directly. Columns are checked against the table before they
8897/// are quoted, so an unknown or crafted name is refused rather than
8898/// interpolated.
8899fn order_terms(
8900    order: &serde_json::Value,
8901    schema_cache: &SchemaCache,
8902    schema_name: &str,
8903    table_name: &str,
8904    alias: &str,
8905    names: &crate::names::NameOverrides,
8906) -> Result<Option<String>, async_graphql::Error> {
8907    let entries: Vec<&serde_json::Value> = match order {
8908        serde_json::Value::Array(items) => items.iter().collect(),
8909        serde_json::Value::Object(_) => vec![order],
8910        _ => return Ok(None),
8911    };
8912
8913    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8914    let table = schema_cache
8915        .get_table(&qi)
8916        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8917
8918    let mut terms = Vec::new();
8919    for entry in entries {
8920        let serde_json::Value::Object(map) = entry else {
8921            continue;
8922        };
8923        for (field, direction) in map {
8924            // A direction given as null is no direction, as at the root.
8925            if direction.is_null() {
8926                continue;
8927            }
8928            let column = table_column_for(names, table, field);
8929            if table.get_column(column).is_none() {
8930                return Err(async_graphql::Error::new(format!(
8931                    "cannot order by unknown column \"{}\" on \"{}\"",
8932                    field, table_name
8933                )));
8934            }
8935            let name = direction.as_str().unwrap_or_default();
8936            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8937                async_graphql::Error::new(format!("\"{}\" is not a sort direction", name))
8938            })?;
8939            terms.push(format!(
8940                "{}.{} {}",
8941                postrust_sql::escape_ident(alias),
8942                postrust_sql::escape_ident(column),
8943                sql
8944            ));
8945        }
8946    }
8947
8948    Ok(if terms.is_empty() {
8949        None
8950    } else {
8951        Some(terms.join(", "))
8952    })
8953}
8954
8955/// Whether a to-one relationship always has a row at the other end.
8956///
8957/// It does when this row's own key columns cannot be null: a foreign key
8958/// guarantees a matching row for every value, so a `NOT NULL` key is a
8959/// relationship that cannot be absent, and the field is non-null. A client
8960/// generating types then gets `author` rather than `author | null`, which is
8961/// what the schema actually promises.
8962///
8963/// Only where a foreign key says so. A computed relationship is a function
8964/// call, and a one-to-one reached through the *child's* key is a row that may
8965/// simply not exist.
8966fn always_present(rel: &RelationshipField, table: &postrust_core::schema_cache::Table) -> bool {
8967    use postrust_core::schema_cache::{Cardinality, Relationship};
8968    let Relationship::ForeignKey { cardinality, .. } = &rel.relationship else {
8969        return false;
8970    };
8971    match cardinality {
8972        Cardinality::M2O { .. } => {}
8973        Cardinality::O2O {
8974            is_parent: false, ..
8975        } => {}
8976        _ => return false,
8977    }
8978    let columns = cardinality.columns();
8979    !columns.is_empty()
8980        && columns.iter().all(|(local, _)| {
8981            table
8982                .get_column(local)
8983                .is_some_and(|column| !column.nullable)
8984        })
8985}
8986
8987/// Whether the row at the other end of a relationship carries the key.
8988///
8989/// If it does, this row is written first and its key is pushed down; if this
8990/// row's own column carries it, the other row is written first and its key is
8991/// read back. Cardinality alone does not answer it: a one-to-one is one row in
8992/// either direction, and which side holds the foreign key is the whole
8993/// difference between the two orders.
8994fn child_holds_the_key(relationship: &postrust_core::schema_cache::Relationship) -> bool {
8995    use postrust_core::schema_cache::{Cardinality, Relationship};
8996    match relationship {
8997        Relationship::ForeignKey { cardinality, .. } => match cardinality {
8998            Cardinality::M2O { .. } => false,
8999            Cardinality::O2O { is_parent, .. } => *is_parent,
9000            _ => true,
9001        },
9002        Relationship::Computed { .. } => true,
9003    }
9004}
9005
9006/// The name of a function's session parameter, if it has one.
9007fn computed_session_argument(
9008    cache: &SchemaCache,
9009    function: &postrust_core::api_request::QualifiedIdentifier,
9010    row_argument: &str,
9011) -> Option<String> {
9012    let _ = row_argument;
9013    cache
9014        .routines
9015        .get(function)
9016        .into_iter()
9017        .flatten()
9018        .find(|routine| routine.name == function.name)?
9019        .params
9020        .iter()
9021        .find(|param| {
9022            param.name == "hasura_session" && matches!(param.param_type.as_str(), "json" | "jsonb")
9023        })
9024        .map(|param| param.name.clone())
9025}
9026
9027/// The name of the input holding a computed relationship's own arguments.
9028fn computed_args_type_name(type_name: &str, field: &str) -> String {
9029    format!("{}_{}_args", type_name, field)
9030}
9031
9032/// The argument list a computed relationship's function is called with.
9033///
9034/// `None` where the parent row is the only thing it takes -- the ordinary case,
9035/// and the positional call the REST surface writes. Anything else is written by
9036/// name: the row, the session where the function asks for one, and whatever the
9037/// client put under `args`.
9038fn computed_arguments(
9039    relationship: &RelationshipField,
9040    plan: &postrust_core::embed::EmbedPlan,
9041    parent_row: &str,
9042    given: Option<&serde_json::Value>,
9043    param_idx: &mut usize,
9044    values: &mut Vec<serde_json::Value>,
9045    schema_cache: &SchemaCache,
9046) -> Result<Option<String>, async_graphql::Error> {
9047    let (Some(function), Some(row_argument)) = (&plan.function, &plan.row_argument) else {
9048        return Ok(None);
9049    };
9050    let session = schema_cache
9051        .routines
9052        .get(function)
9053        .into_iter()
9054        .flatten()
9055        .find(|routine| routine.name == function.name)
9056        .and_then(|routine| {
9057            routine
9058                .params
9059                .iter()
9060                .find(|param| {
9061                    param.name == "hasura_session"
9062                        && matches!(param.param_type.as_str(), "json" | "jsonb")
9063                })
9064                .map(|param| param.name.clone())
9065        });
9066    if relationship.arguments.is_empty() && session.is_none() {
9067        return Ok(None);
9068    }
9069
9070    let mut passed = vec![format!(
9071        "{} => {}",
9072        postrust_sql::escape_ident(row_argument),
9073        parent_row
9074    )];
9075    if let Some(session) = session {
9076        passed.push(format!(
9077            "{} => {}",
9078            postrust_sql::escape_ident(&session),
9079            SESSION_ARGUMENT
9080        ));
9081    }
9082    for (name, pg_type, required) in &relationship.arguments {
9083        match given.and_then(|given| given.get(name)) {
9084            None if *required => {
9085                return Err(async_graphql::Error::new(format!(
9086                    "{} needs the argument \"{}\"",
9087                    relationship.name, name
9088                )))
9089            }
9090            // Left out is left out, so the function's own default applies.
9091            None => continue,
9092            Some(value) => {
9093                passed.push(format!(
9094                    "{} => ${}::{}",
9095                    postrust_sql::escape_ident(name),
9096                    param_idx,
9097                    pg_type
9098                ));
9099                *param_idx += 1;
9100                values.push(value.clone());
9101            }
9102        }
9103    }
9104    Ok(Some(passed.join(", ")))
9105}
9106
9107/// The arguments written on an embedded field, as JSON.
9108fn embed_arguments(field: async_graphql::SelectionField<'_>) -> HashMap<String, serde_json::Value> {
9109    field
9110        .arguments()
9111        .map(|args| {
9112            args.into_iter()
9113                .map(|(name, value)| {
9114                    (
9115                        name.to_string(),
9116                        plain_numbers(value.into_json().unwrap_or(serde_json::Value::Null)),
9117                    )
9118                })
9119                .collect()
9120        })
9121        .unwrap_or_default()
9122}
9123
9124/// What an embed's arguments narrow its child rows to: a `DISTINCT ON`
9125/// prefix, a predicate, an ordering, a limit and an offset, in the order the
9126/// SQL takes them.
9127type Narrowing = (String, Option<String>, Option<String>, Option<i64>, i64);
9128
9129/// What an embed's arguments narrow its child rows to.
9130///
9131/// A list and an aggregate of that same list take the same four arguments and
9132/// mean the same thing by them; only what is done with the surviving rows
9133/// differs, so the reading of the arguments is shared.
9134#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
9135fn embed_narrowing(
9136    arguments: &HashMap<String, serde_json::Value>,
9137    plan: &postrust_core::embed::EmbedPlan,
9138    schema_cache: &SchemaCache,
9139    relationships: &HashMap<String, Vec<RelationshipField>>,
9140    target_type: &str,
9141    child_alias: &str,
9142    max_rows: Option<i64>,
9143    param_idx: &mut usize,
9144    values: &mut Vec<serde_json::Value>,
9145    names: &crate::names::NameOverrides,
9146    caller: &crate::role::Caller<'_>,
9147) -> Result<Narrowing, async_graphql::Error> {
9148    // The child's own permission, which is the whole reason an embed is a
9149    // place this has to be applied: `author { articles { ... } }` reads two
9150    // tables, and the rule on the second is not the rule on the first.
9151    let permission =
9152        permission_predicate(caller, names, &plan.foreign_schema, &plan.foreign_table)?;
9153    let requested = arguments.get("where").filter(|v| !v.is_null()).cloned();
9154    let child_where = match and_predicates(requested, permission) {
9155        Some(expression) => {
9156            let child_scope = WhereScope::for_alias(
9157                &plan.foreign_schema,
9158                &plan.foreign_table,
9159                child_alias,
9160                target_type,
9161                schema_cache,
9162                relationships,
9163                names,
9164            )
9165            .for_caller(*caller);
9166            let mut nested_alias = 0usize;
9167            build_condition(
9168                &expression,
9169                &child_scope,
9170                param_idx,
9171                values,
9172                &mut nested_alias,
9173            )?
9174        }
9175        None => None,
9176    };
9177
9178    let child_order = match arguments.get("order_by") {
9179        Some(order) if !order.is_null() => order_terms(
9180            order,
9181            schema_cache,
9182            &plan.foreign_schema,
9183            &plan.foreign_table,
9184            child_alias,
9185            names,
9186        )?,
9187        _ => None,
9188    };
9189
9190    // A limit written on the embed is what the client asked for; the
9191    // configured ceiling still applies as an upper bound, the same way it
9192    // does at the top level -- and so does the child's own permission, which
9193    // bounds rows per parent here rather than per query.
9194    let ceiling = match (
9195        max_rows,
9196        crate::role::read_limit(caller, names, &plan.foreign_schema, &plan.foreign_table),
9197    ) {
9198        (Some(configured), Some(granted)) => Some(configured.min(granted)),
9199        (only, None) | (None, only) => only,
9200    };
9201    let child_limit = match arguments.get("limit").and_then(|v| v.as_i64()) {
9202        Some(requested) => match ceiling {
9203            Some(ceiling) => Some(requested.min(ceiling)),
9204            None => Some(requested),
9205        },
9206        None => ceiling,
9207    };
9208    let child_offset = arguments
9209        .get("offset")
9210        .and_then(|v| v.as_i64())
9211        .unwrap_or(0);
9212
9213    // `author { articles(distinct_on: [title], order_by: {title: asc}) }` --
9214    // one article per title, the same question the root field answers and
9215    // under the same rule about what may be ordered by.
9216    let requested = match arguments.get("distinct_on") {
9217        Some(serde_json::Value::Array(items)) => items
9218            .iter()
9219            .filter_map(|item| item.as_str())
9220            .collect::<Vec<_>>(),
9221        Some(serde_json::Value::String(one)) => vec![one.as_str()],
9222        _ => Vec::new(),
9223    };
9224    let mut distinct_on = Vec::with_capacity(requested.len());
9225    if !requested.is_empty() {
9226        let qi = postrust_core::api_request::QualifiedIdentifier::new(
9227            &plan.foreign_schema,
9228            &plan.foreign_table,
9229        );
9230        let table = schema_cache.get_table(&qi).ok_or_else(|| {
9231            async_graphql::Error::new(format!("unknown table \"{}\"", plan.foreign_table))
9232        })?;
9233        for field in requested {
9234            let column = table_column_for(names, table, field).to_string();
9235            if table.get_column(&column).is_none() {
9236                return Err(async_graphql::Error::new(format!(
9237                    "cannot take distinct on unknown column \"{}\" of \"{}\"",
9238                    field, plan.foreign_table
9239                )));
9240            }
9241            distinct_on.push(format!(
9242                "{}.{}",
9243                postrust_sql::escape_ident(child_alias),
9244                postrust_sql::escape_ident(&column)
9245            ));
9246        }
9247    }
9248    let (distinct, child_order) = distinct_on_clause(&distinct_on, child_order.as_deref())?;
9249
9250    Ok((
9251        distinct,
9252        child_where,
9253        child_order,
9254        child_limit,
9255        child_offset,
9256    ))
9257}
9258
9259#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
9260fn build_embed_expressions<'a>(
9261    caller: &crate::role::Caller<'_>,
9262    schema_cache: &SchemaCache,
9263    relationships: &HashMap<String, Vec<RelationshipField>>,
9264    type_name: &str,
9265    parent_alias: &str,
9266    fields: impl IntoIterator<Item = SelectionField<'a>>,
9267    max_rows: Option<i64>,
9268    alias_counter: &mut usize,
9269    param_idx: &mut usize,
9270    values: &mut Vec<serde_json::Value>,
9271    names: &crate::names::NameOverrides,
9272) -> Result<Vec<(String, String)>, async_graphql::Error> {
9273    let Some(available) = relationships.get(type_name) else {
9274        return Ok(Vec::new());
9275    };
9276
9277    let mut expressions = Vec::new();
9278
9279    for field in fields {
9280        // `<relationship>_aggregate` is the same embed with an aggregate
9281        // select list, so it is resolved here rather than by a resolver of its
9282        // own -- the correlation is what makes it a per-parent answer.
9283        let aggregate_of = field
9284            .name()
9285            .strip_suffix("_aggregate")
9286            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list));
9287
9288        if let Some(rel) = aggregate_of {
9289            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9290                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9291            *alias_counter += 1;
9292            let child_alias = format!("a{}", alias_counter);
9293
9294            // The same narrowing an embedded list takes. `where` here decides
9295            // which rows are counted, not which are returned, so it goes on the
9296            // set the aggregate reads rather than on the answer.
9297            let arguments = embed_arguments(field);
9298            let (child_distinct, child_where, child_order, child_limit, child_offset) =
9299                embed_narrowing(
9300                    &arguments,
9301                    &plan,
9302                    schema_cache,
9303                    relationships,
9304                    &rel.target_type,
9305                    &child_alias,
9306                    max_rows,
9307                    param_idx,
9308                    values,
9309                    names,
9310                    caller,
9311                )?;
9312
9313            let child_table =
9314                schema_cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
9315                    &plan.foreign_schema,
9316                    &plan.foreign_table,
9317                ));
9318            let select = nested_aggregate_select(
9319                caller,
9320                field,
9321                &child_alias,
9322                schema_cache,
9323                relationships,
9324                &rel.target_type,
9325                child_table,
9326                max_rows,
9327                alias_counter,
9328                param_idx,
9329                values,
9330                names,
9331            )?;
9332
9333            let call = computed_arguments(
9334                rel,
9335                &plan,
9336                &postrust_sql::escape_ident(parent_alias),
9337                arguments.get("args"),
9338                param_idx,
9339                values,
9340                schema_cache,
9341            )?;
9342            let expression = plan
9343                .aggregate_expression(
9344                    parent_alias,
9345                    &postrust_sql::escape_ident(parent_alias),
9346                    &child_alias,
9347                    &select,
9348                    child_limit,
9349                    child_offset,
9350                    child_where.as_deref(),
9351                    child_order.as_deref(),
9352                    call.as_deref(),
9353                    &child_distinct,
9354                    // A GraphQL body is JSON throughout -- nothing here
9355                    // renders a row as text -- and Hasura keeps the order the
9356                    // fields were selected in.
9357                    postrust_core::EmbedRendering::Verbatim,
9358                )
9359                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9360            expressions.push((field.name().to_string(), expression));
9361            continue;
9362        }
9363
9364        let Some(rel) = available.iter().find(|r| r.name == field.name()) else {
9365            continue;
9366        };
9367
9368        let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9369            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9370
9371        *alias_counter += 1;
9372        let child_alias = format!("e{}", alias_counter);
9373
9374        let nested = build_embed_expressions(
9375            caller,
9376            schema_cache,
9377            relationships,
9378            &rel.target_type,
9379            &child_alias,
9380            field.selection_set(),
9381            max_rows,
9382            alias_counter,
9383            param_idx,
9384            values,
9385            names,
9386        )?;
9387
9388        // The arguments written on the embed itself.
9389        let arguments = embed_arguments(field);
9390        let (child_distinct, child_where, child_order, child_limit, child_offset) =
9391            embed_narrowing(
9392                &arguments,
9393                &plan,
9394                schema_cache,
9395                relationships,
9396                &rel.target_type,
9397                &child_alias,
9398                max_rows,
9399                param_idx,
9400                values,
9401                names,
9402                caller,
9403            )?;
9404
9405        // Leaf fields are columns; anything that resolved to a relationship is
9406        // an expression instead.
9407        let child_relationships = relationships.get(&rel.target_type);
9408        let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
9409            &plan.foreign_schema,
9410            &plan.foreign_table,
9411        );
9412        let child_table = schema_cache.get_table(&child_qi);
9413        let mut parts: Vec<String> = Vec::new();
9414        for sub in field.selection_set() {
9415            let name = sub.name();
9416            let is_relationship = child_relationships
9417                .map(|rels| {
9418                    rels.iter().any(|r| {
9419                        r.name == name || (r.is_list && format!("{}_aggregate", r.name) == name)
9420                    })
9421                })
9422                .unwrap_or(false);
9423            // `__typename` is answered by the schema, not the row -- and it is
9424            // asked for here constantly: Apollo Client adds it to every
9425            // selection, and a Federation router to every entity reference.
9426            if is_relationship || name == "__typename" {
9427                continue;
9428            }
9429            // A computed column inside an embed is the same call, against the
9430            // child's alias rather than the table.
9431            let column = child_table
9432                .map(|t| table_column_for(names, t, name))
9433                .unwrap_or(name);
9434            let computed = child_table
9435                .filter(|t| t.get_column(column).is_none())
9436                .and_then(|t| {
9437                    let function = names
9438                        .computed_source(&t.schema, &t.name, name)
9439                        .unwrap_or(name);
9440                    t.get_computed_column(function)
9441                });
9442            match computed {
9443                Some(definition) => parts.push(format!(
9444                    "{} AS {}",
9445                    computed_call(
9446                        definition,
9447                        &format!("{}.*", postrust_sql::escape_ident(&child_alias)),
9448                        &postrust_sql::escape_ident(&child_alias),
9449                    ),
9450                    postrust_sql::escape_ident(name)
9451                )),
9452                // A column the child exposes under another name is selected as
9453                // that name, since the row it lands in is the client's.
9454                None if column != name => parts.push(format!(
9455                    "{} AS {}",
9456                    postrust_sql::escape_ident(column),
9457                    postrust_sql::escape_ident(name)
9458                )),
9459                None => parts.push(postrust_sql::escape_ident(name)),
9460            }
9461        }
9462        if parts.is_empty() && nested.is_empty() {
9463            parts.push(
9464                child_table
9465                    .and_then(|table| rename_projection(table, &child_alias, names))
9466                    .unwrap_or_else(|| format!("{}.*", postrust_sql::escape_ident(&child_alias))),
9467            );
9468        }
9469        for (field_name, expression) in nested {
9470            parts.push(format!(
9471                "{} AS {}",
9472                expression,
9473                postrust_sql::escape_ident(&field_name)
9474            ));
9475        }
9476
9477        let computed_call_arguments = computed_arguments(
9478            rel,
9479            &plan,
9480            &postrust_sql::escape_ident(parent_alias),
9481            arguments.get("args"),
9482            param_idx,
9483            values,
9484            schema_cache,
9485        )?;
9486        let expression = plan
9487            .embed_expression(
9488                parent_alias,
9489                // A computed relationship is correlated by argument rather
9490                // than by a key: the function takes the parent row, and an
9491                // alias names that row.
9492                &postrust_sql::escape_ident(parent_alias),
9493                &child_alias,
9494                &format!("{}{}", child_distinct, parts.join(", ")),
9495                child_limit,
9496                child_offset,
9497                child_where.as_deref(),
9498                child_order.as_deref(),
9499                computed_call_arguments.as_deref(),
9500                postrust_core::EmbedRendering::Verbatim,
9501            )
9502            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9503
9504        expressions.push((rel.name.clone(), expression));
9505    }
9506
9507    Ok(expressions)
9508}
9509
9510/// What embedding a relationship needs, independent of the rows involved.
9511///
9512/// The connection is passed alongside rather than held here: embedding runs on
9513/// the request's transaction, and a shared reference to this struct could not
9514/// hand out the mutable borrow the queries need.
9515struct EmbedContext<'c> {
9516    schema_cache: &'c SchemaCache,
9517    relationships: &'c HashMap<String, Vec<RelationshipField>>,
9518    max_rows: Option<i64>,
9519}
9520
9521/// Embed the relationship fields requested on `rows`.
9522///
9523/// One query per relationship per level, not one per row: the parents' join
9524/// keys are collected and passed as a single array. Recurses so a nested
9525/// selection costs one further query per relationship at each depth.
9526fn embed_relationships<'f>(
9527    conn: &'f mut sqlx::PgConnection,
9528    ctx: &'f EmbedContext<'f>,
9529    type_name: &'f str,
9530    selection: async_graphql::SelectionField<'f>,
9531    rows: &'f mut [serde_json::Value],
9532) -> futures::future::BoxFuture<'f, Result<(), async_graphql::Error>> {
9533    Box::pin(async move {
9534        if rows.is_empty() {
9535            return Ok(());
9536        }
9537
9538        let Some(available) = ctx.relationships.get(type_name) else {
9539            return Ok(());
9540        };
9541
9542        for requested in selection.selection_set() {
9543            let Some(rel) = available.iter().find(|r| r.name == requested.name()) else {
9544                continue;
9545            };
9546
9547            let plan =
9548                postrust_core::embed::EmbedPlan::resolve(&rel.relationship, ctx.schema_cache)
9549                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9550
9551            let keys = postrust_core::embed::parent_keys(rows, &plan.local_column);
9552
9553            // Project only what the selection asked for. A GraphQL selection
9554            // names its leaf fields, so the columns are known before the query
9555            // and an unrequested column need not be read, serialised, sent and
9556            // parsed just to be dropped.
9557            //
9558            // A nested relationship joins on a column of the child row, so that
9559            // column is added even when it was not selected; it is removed again
9560            // when the response is shaped. A selection whose sub-fields cannot
9561            // all be resolved to columns falls back to every column.
9562            let mut child_columns: Vec<String> = Vec::new();
9563            let mut project_everything = false;
9564            let child_relationships = ctx.relationships.get(&rel.target_type);
9565            for field in requested.selection_set() {
9566                let name = field.name();
9567                match child_relationships.and_then(|rels| rels.iter().find(|r| r.name == name)) {
9568                    Some(nested_rel) => {
9569                        match postrust_core::embed::EmbedPlan::resolve(
9570                            &nested_rel.relationship,
9571                            ctx.schema_cache,
9572                        ) {
9573                            Ok(nested_plan) => child_columns.push(nested_plan.local_column),
9574                            Err(_) => project_everything = true,
9575                        }
9576                    }
9577                    // Answered by the schema, not a column.
9578                    None if name == "__typename" => {}
9579                    None => child_columns.push(name.to_string()),
9580                }
9581            }
9582            if project_everything || child_columns.is_empty() {
9583                child_columns.clear();
9584            }
9585
9586            let mut grouped = if keys.is_empty() {
9587                std::collections::HashMap::new()
9588            } else {
9589                let sql = plan
9590                    .children_grouped_sql(ctx.max_rows, &child_columns)
9591                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9592
9593                let fetched = sqlx::query(&sql)
9594                    .bind(&keys)
9595                    .fetch_all(&mut *conn)
9596                    .await
9597                    .map_err(database_error)?;
9598
9599                // The query returns the join key and a JSON array of that key's
9600                // children, grouped by PostgreSQL rather than row by row here.
9601                use sqlx::Row;
9602                let pairs: Vec<(serde_json::Value, serde_json::Value)> = fetched
9603                    .into_iter()
9604                    .filter_map(|row| {
9605                        Some((
9606                            row.try_get::<serde_json::Value, _>(0).ok()?,
9607                            row.try_get::<serde_json::Value, _>(1).ok()?,
9608                        ))
9609                    })
9610                    .collect();
9611
9612                postrust_core::embed::group_from_aggregated(pairs)
9613            };
9614
9615            // Recurse before attaching, so nested embeds land in the values
9616            // copied onto the parents. One query serves every child row at this
9617            // level, so the rows are flattened for the call and put back
9618            // afterwards; skipped when the selection asks for nothing deeper.
9619            let has_deeper_embed = ctx
9620                .relationships
9621                .get(&rel.target_type)
9622                .map(|rels| {
9623                    requested
9624                        .selection_set()
9625                        .any(|field| rels.iter().any(|r| r.name == field.name()))
9626                })
9627                .unwrap_or(false);
9628
9629            if has_deeper_embed {
9630                let mut order: Vec<(String, usize)> = Vec::with_capacity(grouped.len());
9631                let mut flat: Vec<serde_json::Value> = Vec::new();
9632                for (key, children) in grouped.drain() {
9633                    order.push((key, children.len()));
9634                    flat.extend(children);
9635                }
9636
9637                embed_relationships(&mut *conn, ctx, &rel.target_type, requested, &mut flat)
9638                    .await?;
9639
9640                let mut rest = flat.into_iter();
9641                for (key, count) in order {
9642                    grouped.insert(key, rest.by_ref().take(count).collect());
9643                }
9644            }
9645
9646            for row in rows.iter_mut() {
9647                postrust_core::embed::attach_to_parent(row, &rel.name, &plan, &grouped);
9648            }
9649        }
9650
9651        Ok(())
9652    })
9653}
9654
9655/// Build a `where` document that addresses exactly one row by primary key.
9656///
9657/// Used by the by-PK mutations, which take the key columns as arguments instead
9658/// of a free-form `where`.
9659fn pk_where_from_args(
9660    ctx: &ResolverContext<'_>,
9661    schema_name: &str,
9662    table_name: &str,
9663    pk_columns: &[(String, String)],
9664    names: &crate::names::NameOverrides,
9665) -> Result<serde_json::Value, async_graphql::Error> {
9666    if pk_columns.is_empty() {
9667        return Err(async_graphql::Error::new(format!(
9668            "\"{}\" has no primary key, so it cannot be mutated by key",
9669            table_name
9670        )));
9671    }
9672
9673    // `update_x_by_pk` takes its key as one `pk_columns` object; the others
9674    // take the key columns as arguments of their own. Both spellings are what
9675    // a client was generated against, so both are read here.
9676    let from_object = ctx
9677        .args
9678        .try_get("pk_columns")
9679        .ok()
9680        .map(|v| accessor_to_json(&v));
9681
9682    let mut conditions = serde_json::Map::new();
9683    for (col_name, _) in pk_columns {
9684        // Under the name the key column is exposed as, on both sides: the
9685        // argument the client wrote, and the predicate handed on to the same
9686        // builder every other `where` goes through.
9687        let field = names
9688            .column(schema_name, table_name, col_name)
9689            .unwrap_or(col_name);
9690        let value = match &from_object {
9691            Some(serde_json::Value::Object(map)) => map.get(field).cloned().ok_or_else(|| {
9692                async_graphql::Error::new(format!(
9693                    "pk_columns is missing the key column \"{}\"",
9694                    field
9695                ))
9696            })?,
9697            _ => {
9698                let arg = ctx.args.try_get(field).map_err(|_| {
9699                    async_graphql::Error::new(format!(
9700                        "missing required primary key argument \"{}\"",
9701                        field
9702                    ))
9703                })?;
9704                accessor_to_json(&arg)
9705            }
9706        };
9707        conditions.insert(field.to_string(), serde_json::json!({ "_eq": value }));
9708    }
9709
9710    Ok(serde_json::Value::Object(conditions))
9711}
9712
9713/// GraphQL scalar name to use for a primary key argument of the given
9714/// PostgreSQL type.
9715///
9716/// Falls back to `String` for anything that does not map to a plain scalar --
9717/// a composite or array key cannot be expressed as a single named argument, and
9718/// the value is cast to the column's type in SQL anyway.
9719fn pk_argument_type(pg_type: &str) -> String {
9720    let rendered = crate::types::pg_type_to_graphql(pg_type).to_string();
9721    if rendered.starts_with('[') {
9722        "String".to_string()
9723    } else {
9724        rendered
9725    }
9726}
9727
9728/// The public GraphQL type of a primary-key argument.
9729///
9730/// SQL execution still carries the PostgreSQL type beside the key and uses it
9731/// for casts and binding. This is only the schema-facing type.
9732fn exposed_pk_argument_type(
9733    names: &crate::names::NameOverrides,
9734    schema: &str,
9735    table: &str,
9736    column: &str,
9737    pg_type: &str,
9738) -> String {
9739    names
9740        .column_type(schema, table, column)
9741        .map(|given| given.to_string())
9742        .unwrap_or_else(|| pk_argument_type(pg_type))
9743}
9744
9745/// Convert a GraphQL type string to a TypeRef.
9746fn graphql_type_ref(type_str: &str) -> TypeRef {
9747    // Parse type string like "[Users!]!" or "String" or "Int!"
9748    let is_list = type_str.starts_with('[');
9749    let is_nn = type_str.ends_with('!');
9750
9751    // Strip outer modifiers: first the trailing !, then the brackets
9752    let inner = if is_list {
9753        let stripped = type_str
9754            .trim_end_matches('!') // Remove outer !
9755            .trim_start_matches('[') // Remove [
9756            .trim_end_matches(']'); // Remove ]
9757        stripped
9758    } else {
9759        type_str.trim_end_matches('!')
9760    };
9761
9762    let inner_nn = inner.ends_with('!');
9763    let base_type = inner.trim_end_matches('!');
9764
9765    if is_list {
9766        if is_nn {
9767            if inner_nn {
9768                TypeRef::named_nn_list_nn(base_type)
9769            } else {
9770                TypeRef::named_list_nn(base_type)
9771            }
9772        } else if inner_nn {
9773            TypeRef::named_nn_list(base_type)
9774        } else {
9775            TypeRef::named_list(base_type)
9776        }
9777    } else if is_nn {
9778        TypeRef::named_nn(base_type)
9779    } else {
9780        TypeRef::named(base_type)
9781    }
9782}
9783
9784/// The key serde_json wraps a number in when it is keeping its digits.
9785///
9786/// This crate reads `serde_json` with `arbitrary_precision`, so a PostgreSQL
9787/// `numeric` survives a round trip through JSON with every digit it had. The
9788/// cost is on the way in: a fractional number in a *variable* is deserialized
9789/// into async-graphql's own value type as a one-key object holding the text,
9790/// because the generic path cannot know it is looking at a number. Integers
9791/// are unaffected, which is why this went unnoticed until a shape arrived by
9792/// variable and every coordinate in it read as zero.
9793const NUMBER_AS_TEXT: &str = "$serde_json::private::Number";
9794
9795/// The number an arbitrary-precision wrapper is holding, if that is what this
9796/// object is.
9797fn number_kept_as_text(
9798    map: &serde_json::Map<String, serde_json::Value>,
9799) -> Option<serde_json::Value> {
9800    if map.len() != 1 {
9801        return None;
9802    }
9803    let text = map.get(NUMBER_AS_TEXT)?.as_str()?;
9804    serde_json::from_str::<serde_json::Value>(text)
9805        .ok()
9806        .filter(|value| value.is_number())
9807}
9808
9809/// The same, applied to a whole document that came in as one.
9810fn plain_numbers(value: serde_json::Value) -> serde_json::Value {
9811    match value {
9812        serde_json::Value::Object(map) => match number_kept_as_text(&map) {
9813            Some(number) => number,
9814            None => serde_json::Value::Object(
9815                map.into_iter()
9816                    .map(|(k, v)| (k, plain_numbers(v)))
9817                    .collect(),
9818            ),
9819        },
9820        serde_json::Value::Array(items) => {
9821            serde_json::Value::Array(items.into_iter().map(plain_numbers).collect())
9822        }
9823        other => other,
9824    }
9825}
9826
9827/// Convert ValueAccessor to JSON.
9828fn accessor_to_json(accessor: &ValueAccessor<'_>) -> serde_json::Value {
9829    // Use the deserialize method if available, or convert manually
9830    if accessor.is_null() {
9831        serde_json::Value::Null
9832    } else if let Ok(b) = accessor.boolean() {
9833        serde_json::Value::Bool(b)
9834    } else if let Ok(i) = accessor.i64() {
9835        serde_json::Value::Number(i.into())
9836    } else if let Ok(f) = accessor.f64() {
9837        serde_json::Number::from_f64(f)
9838            .map(serde_json::Value::Number)
9839            .unwrap_or(serde_json::Value::Null)
9840    } else if let Ok(s) = accessor.string() {
9841        serde_json::Value::String(s.to_string())
9842    } else if let Ok(name) = accessor.enum_name() {
9843        // An enum value is not a string to `string()`, and falling through to
9844        // null made every `order_by: {name: asc}` read as an empty direction.
9845        serde_json::Value::String(name.to_string())
9846    } else if let Ok(list) = accessor.list() {
9847        serde_json::Value::Array(list.iter().map(|v| accessor_to_json(&v)).collect())
9848    } else if let Ok(obj) = accessor.object() {
9849        let map: serde_json::Map<String, serde_json::Value> = obj
9850            .iter()
9851            .map(|(k, v)| (k.to_string(), accessor_to_json(&v)))
9852            .collect();
9853        match number_kept_as_text(&map) {
9854            Some(number) => number,
9855            None => serde_json::Value::Object(map),
9856        }
9857    } else {
9858        serde_json::Value::Null
9859    }
9860}
9861
9862/// Convert async-graphql Value to JSON.
9863fn value_to_json(value: &Value) -> serde_json::Value {
9864    match value {
9865        Value::Null => serde_json::Value::Null,
9866        Value::Boolean(b) => serde_json::Value::Bool(*b),
9867        Value::Number(n) => {
9868            if let Some(i) = n.as_i64() {
9869                serde_json::Value::Number(i.into())
9870            } else if let Some(f) = n.as_f64() {
9871                serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
9872            } else {
9873                serde_json::Value::Null
9874            }
9875        }
9876        Value::String(s) => serde_json::Value::String(s.clone()),
9877        Value::List(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
9878        Value::Object(obj) => {
9879            let map: serde_json::Map<String, serde_json::Value> = obj
9880                .iter()
9881                .map(|(k, v)| (k.to_string(), value_to_json(v)))
9882                .collect();
9883            serde_json::Value::Object(map)
9884        }
9885        Value::Binary(b) => serde_json::Value::String(base64::Engine::encode(
9886            &base64::engine::general_purpose::STANDARD,
9887            b,
9888        )),
9889        Value::Enum(e) => serde_json::Value::String(e.to_string()),
9890    }
9891}
9892
9893/// Convert JSON to async-graphql Value.
9894fn json_to_value(json: serde_json::Value) -> Value {
9895    match json {
9896        serde_json::Value::Null => Value::Null,
9897        serde_json::Value::Bool(b) => Value::Boolean(b),
9898        serde_json::Value::Number(n) => {
9899            if let Some(i) = n.as_i64() {
9900                Value::Number(i.into())
9901            } else if let Some(f) = n.as_f64() {
9902                Value::Number(async_graphql::Number::from_f64(f).unwrap())
9903            } else {
9904                Value::Null
9905            }
9906        }
9907        serde_json::Value::String(s) => Value::String(s),
9908        serde_json::Value::Array(arr) => Value::List(arr.into_iter().map(json_to_value).collect()),
9909        serde_json::Value::Object(obj) => {
9910            let map: indexmap::IndexMap<async_graphql::Name, Value> = obj
9911                .into_iter()
9912                .map(|(k, v)| (async_graphql::Name::new(k), json_to_value(v)))
9913                .collect();
9914            Value::Object(map)
9915        }
9916    }
9917}
9918
9919/// Register filter input types.
9920///
9921/// These are currently unreachable: the `filter` and `where` arguments are
9922/// declared as the `JSON` scalar, so no field references these input objects
9923/// and async-graphql prunes them from the published schema (introspecting
9924/// `IntFilterInput` returns null). They are kept as the shape to move to if
9925/// filters become typed per column; until then the operators a filter actually
9926/// supports are the ones `build_where_clause` implements, and it rejects
9927/// anything else rather than ignoring it.
9928#[cfg(test)]
9929mod tests {
9930    use super::*;
9931    use indexmap::IndexMap;
9932    use postrust_core::schema_cache::{Cardinality, Column, Relationship, Table};
9933    use postrust_core::QualifiedIdentifier;
9934    use std::collections::{HashMap, HashSet};
9935
9936    fn entity_key_column(pg_type: &str, explicit_id: bool) -> EntityKeyColumn {
9937        EntityKeyColumn {
9938            field_name: "id".to_string(),
9939            column_name: "id".to_string(),
9940            pg_type: pg_type.to_string(),
9941            explicit_id,
9942        }
9943    }
9944
9945    #[test]
9946    fn federation_integer_keys_reject_strings() {
9947        let column = entity_key_column("int4", false);
9948
9949        assert_eq!(
9950            normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap(),
9951            serde_json::json!(2)
9952        );
9953        let error = normalize_entity_key_value("users", &column, &serde_json::json!("2"))
9954            .expect_err("a GraphQL Int cannot be a string");
9955        assert_eq!(
9956            error.message,
9957            "entity representation for \"users\" has invalid key field \"id\": expected a value compatible with PostgreSQL type \"int4\", found a string"
9958        );
9959    }
9960
9961    #[test]
9962    fn federation_bigint_keys_accept_exact_decimal_strings() {
9963        let column = entity_key_column("int8", false);
9964
9965        assert_eq!(
9966            normalize_entity_key_value("users", &column, &serde_json::json!(7)).unwrap(),
9967            serde_json::json!(7)
9968        );
9969        assert_eq!(
9970            normalize_entity_key_value("users", &column, &serde_json::json!("9007199254740993"))
9971                .unwrap(),
9972            serde_json::json!(9_007_199_254_740_993_i64)
9973        );
9974        assert!(normalize_entity_key_value(
9975            "users",
9976            &column,
9977            &serde_json::json!("9223372036854775808")
9978        )
9979        .is_err());
9980        assert!(normalize_entity_key_value("users", &column, &serde_json::json!("7.5")).is_err());
9981    }
9982
9983    #[test]
9984    fn federation_numeric_keys_accept_exact_decimal_strings() {
9985        let column = entity_key_column("numeric", false);
9986
9987        assert_eq!(
9988            normalize_entity_key_value("users", &column, &serde_json::json!(7.5)).unwrap(),
9989            serde_json::json!("7.5")
9990        );
9991        assert_eq!(
9992            normalize_entity_key_value(
9993                "users",
9994                &column,
9995                &serde_json::json!("9007199254740993.123456789")
9996            )
9997            .unwrap(),
9998            serde_json::json!("9007199254740993.123456789")
9999        );
10000        assert!(
10001            normalize_entity_key_value("users", &column, &serde_json::json!("not-a-number"))
10002                .is_err()
10003        );
10004    }
10005
10006    #[test]
10007    fn federation_integer_backed_ids_have_one_normal_form() {
10008        let column = entity_key_column("int4", true);
10009
10010        let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
10011        let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
10012        assert_eq!(number, serde_json::json!(2));
10013        assert_eq!(string, number);
10014        assert!(normalize_entity_key_value("users", &column, &serde_json::json!("two")).is_err());
10015    }
10016
10017    #[test]
10018    fn federation_text_backed_ids_normalize_integer_input_to_text() {
10019        let column = entity_key_column("text", true);
10020
10021        let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
10022        let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
10023        assert_eq!(number, serde_json::json!("2"));
10024        assert_eq!(string, number);
10025    }
10026
10027    #[test]
10028    fn federation_uuid_backed_ids_are_validated_and_canonicalized() {
10029        let column = entity_key_column("uuid", true);
10030
10031        assert_eq!(
10032            normalize_entity_key_value(
10033                "users",
10034                &column,
10035                &serde_json::json!("550E8400-E29B-41D4-A716-446655440000")
10036            )
10037            .unwrap(),
10038            serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
10039        );
10040        assert!(
10041            normalize_entity_key_value("users", &column, &serde_json::json!("not-a-uuid")).is_err()
10042        );
10043    }
10044
10045    fn create_test_table(name: &str) -> Table {
10046        let mut columns = IndexMap::new();
10047        columns.insert(
10048            "id".into(),
10049            Column {
10050                name: "id".into(),
10051                description: None,
10052                nullable: false,
10053                data_type: "integer".into(),
10054                nominal_type: "int4".into(),
10055                max_len: None,
10056                default: Some("nextval('id_seq')".into()),
10057                enum_values: vec![],
10058                is_pk: true,
10059                position: 1,
10060                always_generated: false,
10061                domain_type: None,
10062            },
10063        );
10064        columns.insert(
10065            "name".into(),
10066            Column {
10067                name: "name".into(),
10068                description: None,
10069                nullable: false,
10070                data_type: "text".into(),
10071                nominal_type: "text".into(),
10072                max_len: None,
10073                default: None,
10074                enum_values: vec![],
10075                is_pk: false,
10076                position: 2,
10077                always_generated: false,
10078                domain_type: None,
10079            },
10080        );
10081
10082        Table {
10083            schema: "public".into(),
10084            name: name.into(),
10085            description: None,
10086            is_view: false,
10087            insertable: true,
10088            updatable: true,
10089            deletable: true,
10090            pk_cols: vec!["id".into()],
10091            unique_constraints: Vec::new(),
10092            columns,
10093            computed_columns: Default::default(),
10094            is_partitioned: false,
10095        }
10096    }
10097
10098    fn create_test_schema_cache() -> SchemaCache {
10099        let mut tables = HashMap::new();
10100        let users = create_test_table("users");
10101        tables.insert(users.qualified_identifier(), users);
10102
10103        SchemaCache {
10104            tables,
10105            relationships: HashMap::new(),
10106            routines: HashMap::new(),
10107            timezones: HashSet::new(),
10108            media_handlers: HashMap::new(),
10109            pg_version: 150000,
10110            representations: Default::default(),
10111        }
10112    }
10113
10114    fn add_test_table(cache: &mut SchemaCache, name: &str) {
10115        let table = create_test_table(name);
10116        cache.tables.insert(table.qualified_identifier(), table);
10117    }
10118
10119    fn add_test_posts_relationship(cache: &mut SchemaCache) {
10120        let users = QualifiedIdentifier::new("public", "users");
10121        cache.relationships.insert(
10122            (users.clone(), "public".into()),
10123            vec![Relationship::ForeignKey {
10124                table: users,
10125                foreign_table: QualifiedIdentifier::new("public", "posts"),
10126                is_self: false,
10127                cardinality: Cardinality::O2M {
10128                    constraint: "posts_user_id_fkey".into(),
10129                    columns: vec![("id".into(), "user_id".into())],
10130                },
10131                table_is_view: false,
10132                foreign_table_is_view: false,
10133                constraint_name: "posts_user_id_fkey".into(),
10134            }],
10135        );
10136    }
10137
10138    // ============================================================================
10139    // Type Reference Tests
10140    // ============================================================================
10141
10142    #[test]
10143    fn test_graphql_type_ref_simple() {
10144        let _type_ref = graphql_type_ref("String");
10145        // TypeRef doesn't implement PartialEq, so we just test it doesn't panic
10146    }
10147
10148    #[test]
10149    fn test_graphql_type_ref_non_null() {
10150        let _type_ref = graphql_type_ref("String!");
10151    }
10152
10153    #[test]
10154    fn test_graphql_type_ref_list() {
10155        let _type_ref = graphql_type_ref("[String]");
10156    }
10157
10158    #[test]
10159    fn test_graphql_type_ref_list_non_null() {
10160        let _type_ref = graphql_type_ref("[String!]!");
10161    }
10162
10163    // ============================================================================
10164    // Value Conversion Tests
10165    // ============================================================================
10166
10167    #[test]
10168    fn test_value_to_json_null() {
10169        let value = Value::Null;
10170        let json = value_to_json(&value);
10171        assert_eq!(json, serde_json::Value::Null);
10172    }
10173
10174    #[test]
10175    fn test_value_to_json_boolean() {
10176        let value = Value::Boolean(true);
10177        let json = value_to_json(&value);
10178        assert_eq!(json, serde_json::Value::Bool(true));
10179    }
10180
10181    #[test]
10182    fn test_value_to_json_number() {
10183        let value = Value::Number(42.into());
10184        let json = value_to_json(&value);
10185        assert_eq!(json, serde_json::json!(42));
10186    }
10187
10188    #[test]
10189    fn test_value_to_json_string() {
10190        let value = Value::String("hello".to_string());
10191        let json = value_to_json(&value);
10192        assert_eq!(json, serde_json::Value::String("hello".to_string()));
10193    }
10194
10195    #[test]
10196    fn test_value_to_json_list() {
10197        let value = Value::List(vec![Value::Number(1.into()), Value::Number(2.into())]);
10198        let json = value_to_json(&value);
10199        assert_eq!(json, serde_json::json!([1, 2]));
10200    }
10201
10202    #[test]
10203    fn test_json_to_value_null() {
10204        let json = serde_json::Value::Null;
10205        let value = json_to_value(json);
10206        assert!(matches!(value, Value::Null));
10207    }
10208
10209    #[test]
10210    fn test_json_to_value_boolean() {
10211        let json = serde_json::Value::Bool(false);
10212        let value = json_to_value(json);
10213        assert!(matches!(value, Value::Boolean(false)));
10214    }
10215
10216    #[test]
10217    fn test_json_to_value_number() {
10218        let json = serde_json::json!(123);
10219        let value = json_to_value(json);
10220        assert!(matches!(value, Value::Number(_)));
10221    }
10222
10223    #[test]
10224    fn test_json_to_value_string() {
10225        let json = serde_json::Value::String("test".to_string());
10226        let value = json_to_value(json);
10227        assert!(matches!(value, Value::String(_)));
10228    }
10229
10230    #[test]
10231    fn test_json_to_value_array() {
10232        let json = serde_json::json!([1, 2, 3]);
10233        let value = json_to_value(json);
10234        assert!(matches!(value, Value::List(_)));
10235    }
10236
10237    #[test]
10238    fn test_json_to_value_object() {
10239        let json = serde_json::json!({"key": "value"});
10240        let value = json_to_value(json);
10241        assert!(matches!(value, Value::Object(_)));
10242    }
10243
10244    // ============================================================================
10245    // Schema Building Tests
10246    // ============================================================================
10247
10248    #[test]
10249    fn test_build_dynamic_schema() {
10250        let cache = create_test_schema_cache();
10251        let config = SchemaConfig::default();
10252        let generated = build_schema(&cache, &config);
10253
10254        let result = build_dynamic_schema(
10255            &generated,
10256            &cache,
10257            None,
10258            None,
10259            Arc::new(Default::default()),
10260            std::time::Duration::from_secs(30),
10261            None,
10262        );
10263        if let Err(ref e) = result {
10264            eprintln!("Schema build error: {:?}", e);
10265        }
10266        assert!(result.is_ok(), "Schema build failed: {:?}", result.err());
10267    }
10268
10269    #[test]
10270    fn a_column_type_override_reaches_fields_and_key_arguments() {
10271        let cache = create_test_schema_cache();
10272        let names = crate::names::NameOverrides::parse(
10273            r#"{"tables": {"public.users": {"column_types": {"id": "ID"}}}}"#,
10274        )
10275        .unwrap();
10276        let config = SchemaConfig {
10277            names: names.clone(),
10278            ..SchemaConfig::default()
10279        };
10280        let generated = build_schema(&cache, &config);
10281        let schema = build_dynamic_schema(
10282            &generated,
10283            &cache,
10284            None,
10285            None,
10286            Arc::new(names),
10287            std::time::Duration::from_secs(30),
10288            None,
10289        )
10290        .expect("schema with an ID override should build");
10291        let sdl = schema.sdl();
10292
10293        let input_body = |name: &str| {
10294            sdl.split_once(&format!("input {name} {{"))
10295                .and_then(|(_, rest)| rest.split_once('}'))
10296                .map(|(body, _)| body.to_string())
10297                .unwrap_or_else(|| panic!("missing input {name}:\n{sdl}"))
10298        };
10299
10300        assert!(sdl.contains("id: ID!"), "object and input fields:\n{sdl}");
10301        assert!(
10302            sdl.contains("users_by_pk(id: ID!): users"),
10303            "query key argument:\n{sdl}"
10304        );
10305        assert!(
10306            sdl.contains("delete_users_by_pk(id: ID!): users"),
10307            "delete key argument:\n{sdl}"
10308        );
10309        assert!(
10310            input_body("users_pk_columns_input").contains("id: ID!"),
10311            "update key input:\n{sdl}"
10312        );
10313        assert!(
10314            input_body("users_bool_exp").contains("id: ID_comparison_exp"),
10315            "comparison input:\n{sdl}"
10316        );
10317        assert!(
10318            input_body("users_insert_input").contains("id: ID"),
10319            "write input:\n{sdl}"
10320        );
10321    }
10322
10323    #[test]
10324    fn a_type_prefix_names_the_complete_dynamic_schema() {
10325        let cache = create_test_schema_cache();
10326        let config = SchemaConfig {
10327            type_prefix: Some("test".into()),
10328            ..SchemaConfig::default()
10329        };
10330        let generated = build_schema(&cache, &config);
10331        let schema = build_dynamic_schema(
10332            &generated,
10333            &cache,
10334            None,
10335            None,
10336            Arc::new(Default::default()),
10337            std::time::Duration::from_secs(30),
10338            None,
10339        )
10340        .expect("a prefixed schema should build");
10341        let sdl = schema.sdl();
10342
10343        assert!(sdl.contains("type test_users "), "object type:\n{sdl}");
10344        assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10345        assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10346        assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10347        assert!(
10348            sdl.contains("test_users_aggregate"),
10349            "aggregate type:\n{sdl}"
10350        );
10351        assert!(sdl.contains("test_insert_users("), "mutation root:\n{sdl}");
10352    }
10353
10354    #[test]
10355    fn federation_is_absent_by_default() {
10356        let cache = create_test_schema_cache();
10357        let generated = build_schema(&cache, &SchemaConfig::default());
10358        let schema = build_dynamic_schema(
10359            &generated,
10360            &cache,
10361            None,
10362            None,
10363            Arc::new(Default::default()),
10364            std::time::Duration::from_secs(30),
10365            None,
10366        )
10367        .expect("a non-federated schema should build");
10368        let sdl = schema.sdl();
10369
10370        assert!(!sdl.contains("_Service"), "service type:\n{sdl}");
10371        assert!(!sdl.contains("_service"), "service field:\n{sdl}");
10372        assert!(!sdl.contains("@key"), "federation key:\n{sdl}");
10373    }
10374
10375    #[test]
10376    fn federation_entity_selections_respect_fragment_type_conditions() {
10377        let document = async_graphql::parser::parse_query(
10378            r#"
10379            query {
10380              _entities(representations: []) {
10381                __typename
10382                ... on authors { id items { title } }
10383                ...ShopFields
10384                ... { common }
10385                ...EntityName
10386              }
10387            }
10388            fragment ShopFields on shops { id items { sku } }
10389            fragment EntityName on _Entity { __typename }
10390            "#,
10391        )
10392        .expect("query parses");
10393        let (_, operation) = document.operations.iter().next().expect("one operation");
10394        let entity = operation
10395            .node
10396            .selection_set
10397            .node
10398            .items
10399            .iter()
10400            .find_map(|selection| match &selection.node {
10401                Selection::Field(field) if field.node.name.node == "_entities" => Some(&field.node),
10402                _ => None,
10403            })
10404            .expect("_entities field");
10405
10406        assert_eq!(
10407            entity_field_applicability(&entity.selection_set.node, &document.fragments, "authors"),
10408            vec![true, true, true, false, false, true, true]
10409        );
10410        assert_eq!(
10411            entity_field_applicability(&entity.selection_set.node, &document.fragments, "shops"),
10412            vec![true, false, false, true, true, true, true]
10413        );
10414    }
10415
10416    #[tokio::test]
10417    async fn federation_entity_lists_allow_null_members() {
10418        let entity =
10419            Object::new("widgets").field(Field::new("id", TypeRef::named_nn(TypeRef::INT), |_| {
10420                FieldFuture::new(async { Ok(Some(Value::from(2))) })
10421            }));
10422        let union = Union::new("_Entity").possible_type("widgets");
10423        let query = Object::new("Query").field(Field::new(
10424            "_entities",
10425            TypeRef::named_list_nn("_Entity"),
10426            |_| {
10427                FieldFuture::new(async {
10428                    Ok(Some(FieldValue::list([
10429                        FieldValue::NULL,
10430                        FieldValue::NULL.with_type("widgets"),
10431                    ])))
10432                })
10433            },
10434        ));
10435        let schema = Schema::build("Query", None, None)
10436            .extension(NullableFederationEntity)
10437            .register(entity)
10438            .register(union)
10439            .register(query)
10440            .finish()
10441            .expect("schema builds");
10442
10443        let response = schema
10444            .execute("{ _entities { ... on widgets { id } } }")
10445            .await;
10446
10447        assert!(response.errors.is_empty(), "{:?}", response.errors);
10448        assert_eq!(
10449            response.data,
10450            async_graphql::value!({ "_entities": [null, { "id": 2 }] })
10451        );
10452    }
10453
10454    #[test]
10455    fn federation_metadata_is_byte_for_byte_inert_when_disabled() {
10456        let cache = create_test_schema_cache();
10457        let sdl_for = |config: SchemaConfig| {
10458            let generated = build_schema(&cache, &config);
10459            build_dynamic_schema(
10460                &generated,
10461                &cache,
10462                None,
10463                None,
10464                Arc::new(config.names.clone()),
10465                std::time::Duration::from_secs(30),
10466                None,
10467            )
10468            .expect("schema should build")
10469            .sdl()
10470        };
10471
10472        let baseline = sdl_for(SchemaConfig {
10473            type_prefix: Some("test".into()),
10474            ..SchemaConfig::default()
10475        });
10476        let with_disabled_federation_metadata = sdl_for(SchemaConfig {
10477            enable_federation: false,
10478            type_prefix: Some("test".into()),
10479            names: crate::names::NameOverrides::parse(
10480                r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10481            )
10482            .expect("metadata"),
10483            ..SchemaConfig::default()
10484        });
10485
10486        assert_eq!(with_disabled_federation_metadata, baseline);
10487        assert!(
10488            with_disabled_federation_metadata.contains("type test_users "),
10489            "prefixed object type remains when federation is disabled:\n{with_disabled_federation_metadata}"
10490        );
10491        assert!(
10492            !with_disabled_federation_metadata.contains("_Service"),
10493            "service type:\n{with_disabled_federation_metadata}"
10494        );
10495        assert!(
10496            !with_disabled_federation_metadata.contains("@key"),
10497            "federation key:\n{with_disabled_federation_metadata}"
10498        );
10499    }
10500
10501    #[test]
10502    fn shared_entities_make_relationship_fields_shareable() {
10503        let federation_sdl = |shared: bool| {
10504            let mut cache = create_test_schema_cache();
10505            add_test_table(&mut cache, "posts");
10506            add_test_posts_relationship(&mut cache);
10507            let names = match shared {
10508                true => crate::names::NameOverrides::parse(
10509                    r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10510                )
10511                .expect("metadata"),
10512                false => crate::names::NameOverrides::default(),
10513            };
10514            let config = SchemaConfig {
10515                enable_federation: true,
10516                type_prefix: Some("test".into()),
10517                names: names.clone(),
10518                ..SchemaConfig::default()
10519            };
10520            let generated = build_schema(&cache, &config);
10521            build_dynamic_schema(
10522                &generated,
10523                &cache,
10524                None,
10525                None,
10526                Arc::new(names),
10527                std::time::Duration::from_secs(30),
10528                None,
10529            )
10530            .expect("federated schema should build")
10531            .sdl_with_options(async_graphql::SDLExportOptions::new().federation())
10532        };
10533
10534        let shared = federation_sdl(true);
10535        assert!(
10536            shared.contains("): [test_posts!]! @shareable"),
10537            "shared relationship field:\n{shared}"
10538        );
10539        assert!(
10540            shared.contains("): test_posts_aggregate! @shareable"),
10541            "shared relationship aggregate field:\n{shared}"
10542        );
10543
10544        let not_shared = federation_sdl(false);
10545        assert!(
10546            not_shared.contains("): [test_posts!]!"),
10547            "non-shared relationship field:\n{not_shared}"
10548        );
10549        assert!(
10550            !not_shared.contains("): [test_posts!]! @shareable"),
10551            "non-shared relationship field must not be shareable:\n{not_shared}"
10552        );
10553        assert!(
10554            not_shared.contains("): test_posts_aggregate!"),
10555            "non-shared relationship aggregate field:\n{not_shared}"
10556        );
10557        assert!(
10558            !not_shared.contains("): test_posts_aggregate! @shareable"),
10559            "non-shared relationship aggregate must not be shareable:\n{not_shared}"
10560        );
10561    }
10562
10563    #[tokio::test]
10564    async fn federation_exposes_service_sdl_and_entity_keys() {
10565        let mut cache = create_test_schema_cache();
10566        add_test_table(&mut cache, "posts");
10567        let config = SchemaConfig {
10568            enable_federation: true,
10569            type_prefix: Some("test".into()),
10570            names: crate::names::NameOverrides::parse(
10571                r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10572            )
10573            .expect("metadata"),
10574            ..SchemaConfig::default()
10575        };
10576        let generated = build_schema(&cache, &config);
10577        let schema = build_dynamic_schema(
10578            &generated,
10579            &cache,
10580            None,
10581            None,
10582            Arc::new(Default::default()),
10583            std::time::Duration::from_secs(30),
10584            None,
10585        )
10586        .expect("a federated schema should build");
10587        let sdl = schema.sdl();
10588        let federation_sdl =
10589            schema.sdl_with_options(async_graphql::SDLExportOptions::new().federation());
10590
10591        assert!(sdl.contains("_Service"), "service type:\n{sdl}");
10592        assert!(sdl.contains("_service"), "service field:\n{sdl}");
10593        assert!(
10594            federation_sdl.contains("type users @key(fields: \"id\")"),
10595            "users key:\n{federation_sdl}"
10596        );
10597        assert!(
10598            federation_sdl.contains("@shareable"),
10599            "shared field directive:\n{federation_sdl}"
10600        );
10601        assert!(sdl.contains("type users "), "shared object type:\n{sdl}");
10602        assert!(
10603            !sdl.contains("type test_users "),
10604            "prefixed shared object type:\n{sdl}"
10605        );
10606        assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10607        assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10608        assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10609        assert!(
10610            !sdl.contains("input users_bool_exp "),
10611            "unprefixed derived input:\n{sdl}"
10612        );
10613        assert!(
10614            sdl.contains("test_users_aggregate"),
10615            "aggregate type:\n{sdl}"
10616        );
10617        assert!(
10618            !sdl.contains("type users_aggregate "),
10619            "unprefixed aggregate type:\n{sdl}"
10620        );
10621        assert!(
10622            sdl.contains("test_users_mutation_response"),
10623            "mutation response type:\n{sdl}"
10624        );
10625        assert!(
10626            !sdl.contains("type users_mutation_response "),
10627            "unprefixed mutation response type:\n{sdl}"
10628        );
10629        assert!(
10630            sdl.contains("type test_posts {"),
10631            "non-shared object type remains prefixed:\n{sdl}"
10632        );
10633        assert!(
10634            !sdl.contains("type posts {"),
10635            "non-shared object type must not become unprefixed:\n{sdl}"
10636        );
10637        assert!(sdl.contains("test_posts("), "prefixed posts root:\n{sdl}");
10638        assert!(
10639            sdl.contains("test_posts_bool_exp"),
10640            "prefixed posts bool_exp:\n{sdl}"
10641        );
10642
10643        let response = schema.execute("{ _service { sdl } }").await;
10644        assert!(
10645            response.errors.is_empty(),
10646            "service query errors: {:?}",
10647            response.errors
10648        );
10649        let service = response.data.into_json().expect("service response JSON");
10650        let service_sdl = service
10651            .get("_service")
10652            .and_then(|value| value.get("sdl"))
10653            .and_then(serde_json::Value::as_str)
10654            .expect("service SDL string");
10655        assert!(
10656            service_sdl.contains("type Query"),
10657            "federation root type:\n{service_sdl}"
10658        );
10659        assert!(
10660            service_sdl.contains("test_users("),
10661            "query fields are on the federation root:\n{service_sdl}"
10662        );
10663        assert!(
10664            service_sdl.contains("test_posts("),
10665            "non-shared query fields are on the federation root:\n{service_sdl}"
10666        );
10667        assert!(
10668            !service_sdl.contains("type query_root"),
10669            "Apollo composition treats query_root as an ordinary object without a schema definition:\n{service_sdl}"
10670        );
10671        assert!(
10672            service_sdl.contains("type users @key(fields: \"id\")"),
10673            "{service_sdl}"
10674        );
10675        assert!(
10676            service_sdl.contains("type test_posts @key(fields: \"id\")"),
10677            "{service_sdl}"
10678        );
10679    }
10680
10681    /// `_exists` becomes a subselect over the table it names, correlated with
10682    /// nothing -- which is the difference between it and a relationship.
10683    #[test]
10684    fn exists_is_a_subselect_over_another_table() {
10685        let cache = create_test_schema_cache();
10686        let names = crate::names::NameOverrides::default();
10687        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10688        let scope = WhereScope::table("public", "users", "users", &names)
10689            .with_resolution(&cache, &relationships);
10690
10691        let mut param_idx = 1usize;
10692        let mut values: Vec<serde_json::Value> = Vec::new();
10693        let mut aliases = 0usize;
10694        let sql = build_condition(
10695            &serde_json::json!({
10696                "_exists": {"_table": "users", "_where": {"name": {"_eq": "Ann"}}}
10697            }),
10698            &scope,
10699            &mut param_idx,
10700            &mut values,
10701            &mut aliases,
10702        )
10703        .expect("compiles")
10704        .expect("constrains something");
10705
10706        assert_eq!(
10707            sql,
10708            "EXISTS (SELECT 1 FROM \"public\".\"users\" AS \"pgrst_exists_1\" \
10709             WHERE \"pgrst_exists_1\".\"name\" = $1::text)"
10710        );
10711        assert_eq!(values, vec![serde_json::json!("Ann")]);
10712    }
10713
10714    /// A `_table` nobody has is refused rather than written into the query.
10715    #[test]
10716    fn exists_names_a_table_the_server_has() {
10717        let cache = create_test_schema_cache();
10718        let names = crate::names::NameOverrides::default();
10719        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10720        let scope = WhereScope::table("public", "users", "users", &names)
10721            .with_resolution(&cache, &relationships);
10722        let error = build_condition(
10723            &serde_json::json!({"_exists": {"_table": "nowhere", "_where": {}}}),
10724            &scope,
10725            &mut 1,
10726            &mut Vec::new(),
10727            &mut 0,
10728        )
10729        .expect_err("a table that is not there is an error");
10730        assert!(
10731            error.message.contains("public.nowhere"),
10732            "{}",
10733            error.message
10734        );
10735    }
10736
10737    /// A role granted "how many" and not "which" gets the count and no rows.
10738    ///
10739    /// A select permission naming no columns, with `allow_aggregations`, is
10740    /// Hasura's way of writing that. The table has an aggregate root and no
10741    /// row type, so `nodes` is not there to be a list of one -- and `count`
10742    /// takes no `columns`, because there are none to name.
10743    #[test]
10744    fn a_table_a_role_may_count_and_not_read_has_a_count_and_no_rows() {
10745        let cache = create_test_schema_cache();
10746        let names = crate::names::NameOverrides::parse(
10747            r#"{"tables": {"public.users": {"permissions":
10748                 {"counter": {"select": {"columns": [], "filter": {},
10749                                         "allow_aggregations": true}}}}}}"#,
10750        )
10751        .unwrap();
10752        let view = crate::role::cache_for_role(&cache, &names, "counter", false);
10753        assert!(!view.tables.is_empty(), "the table is there to be counted");
10754
10755        let config = SchemaConfig {
10756            names: names.clone(),
10757            role: Some("counter".to_string()),
10758            ..SchemaConfig::default()
10759        };
10760        let generated = build_schema(&view, &config);
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("counter"),
10769        )
10770        .expect("a role that may only count still has a buildable schema");
10771        let sdl = schema.sdl();
10772
10773        assert!(
10774            sdl.contains("users_aggregate"),
10775            "the count is there:\n{}",
10776            sdl
10777        );
10778        assert!(sdl.contains("count: Int!"), "and it is a count:\n{}", sdl);
10779        // No row type, so nothing that would answer with one.
10780        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10781        assert!(
10782            !sdl.contains("nodes"),
10783            "and no rows beside the numbers:\n{}",
10784            sdl
10785        );
10786        assert!(
10787            !sdl.contains("users_select_column"),
10788            "no column enum to order by or count over:\n{}",
10789            sdl
10790        );
10791        // The functions that read column data are absent with the columns.
10792        assert!(!sdl.contains("users_max_fields"), "no max:\n{}", sdl);
10793    }
10794
10795    /// A role granted `insert` and no `select` gets the write and no type.
10796    ///
10797    /// Hasura's own corpus does this eight times over. What has to hold is
10798    /// that the schema still builds: `insert_users` is there, `users` is not,
10799    /// and the response the insert answers with has no `returning` to name a
10800    /// type that does not exist.
10801    #[test]
10802    fn a_table_a_role_may_only_write_gets_the_write_and_no_type() {
10803        let cache = create_test_schema_cache();
10804        let names = crate::names::NameOverrides::parse(
10805            r#"{"tables": {"public.users": {"permissions":
10806                 {"writer": {"insert": {"columns": "*", "check": {}}}}}}}"#,
10807        )
10808        .unwrap();
10809        let view = crate::role::cache_for_role(&cache, &names, "writer", false);
10810        let config = SchemaConfig {
10811            names: names.clone(),
10812            role: Some("writer".to_string()),
10813            ..SchemaConfig::default()
10814        };
10815        let generated = build_schema(&view, &config);
10816
10817        let schema = build_dynamic_schema(
10818            &generated,
10819            &view,
10820            None,
10821            None,
10822            Arc::new(names),
10823            std::time::Duration::from_secs(30),
10824            Some("writer"),
10825        )
10826        .expect("a role that may only write still has a buildable schema");
10827        let sdl = schema.sdl();
10828
10829        assert!(
10830            sdl.contains("insert_users("),
10831            "the write is there:\n{}",
10832            sdl
10833        );
10834        assert!(
10835            sdl.contains("users_insert_input"),
10836            "and its input:\n{}",
10837            sdl
10838        );
10839        // No row type, and so none of the three fields that answer with one.
10840        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10841        assert!(!sdl.contains("insert_users_one"), "no insert_one:\n{}", sdl);
10842        assert!(
10843            !sdl.contains("returning"),
10844            "and nothing to return:\n{}",
10845            sdl
10846        );
10847        assert!(
10848            sdl.contains("users_mutation_response"),
10849            "the count is still answered:\n{}",
10850            sdl
10851        );
10852        assert!(
10853            !sdl.contains("_mutation_response_mutation_response"),
10854            "and no type is named twice over:\n{}",
10855            sdl
10856        );
10857    }
10858
10859    /// A cache with one VOLATILE function returning rows of `users`, which is
10860    /// the shape `add_to_score` has in Hasura's corpus.
10861    fn cache_with_a_mutation_function() -> SchemaCache {
10862        use postrust_core::schema_cache::{FuncVolatility, RetType, Routine};
10863
10864        let mut cache = create_test_schema_cache();
10865        let routine = Routine {
10866            schema: "public".into(),
10867            name: "add_to_score".into(),
10868            description: None,
10869            params: Vec::new(),
10870            return_type: RetType::SetOf("users".into()),
10871            returns_composite: true,
10872            volatility: FuncVolatility::Volatile,
10873            has_variadic: false,
10874            isolation_level: None,
10875            settings: Vec::new(),
10876            is_procedure: false,
10877            media_type: None,
10878            media_base_type: None,
10879            output_columns: Vec::new(),
10880        };
10881        cache
10882            .routines
10883            .insert(routine.qualified_identifier(), vec![routine]);
10884        cache
10885    }
10886
10887    /// A function with side effects is not granted by permission to read the
10888    /// table it returns. Hasura infers a query function's permission from
10889    /// that select and infers nothing for a mutation, so a role never named
10890    /// by a function permission has no mutation root at all.
10891    #[test]
10892    fn a_mutation_function_needs_the_role_to_be_named() {
10893        let cache = cache_with_a_mutation_function();
10894        let withheld = crate::names::NameOverrides::parse(
10895            r#"{"tables": {"public.users": {"permissions":
10896                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10897                "functions": {"public.add_to_score": {"exposed_as": "mutation"}}}"#,
10898        )
10899        .unwrap();
10900        let granted = crate::names::NameOverrides::parse(
10901            r#"{"tables": {"public.users": {"permissions":
10902                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10903                "functions": {"public.add_to_score":
10904                  {"exposed_as": "mutation", "roles": ["reader"]}}}"#,
10905        )
10906        .unwrap();
10907
10908        let sdl_for = |names: &crate::names::NameOverrides| {
10909            let view = crate::role::cache_for_role(&cache, names, "reader", false);
10910            let config = SchemaConfig {
10911                names: names.clone(),
10912                role: Some("reader".to_string()),
10913                ..SchemaConfig::default()
10914            };
10915            let generated = build_schema(&view, &config);
10916            build_dynamic_schema(
10917                &generated,
10918                &view,
10919                None,
10920                None,
10921                Arc::new(names.clone()),
10922                std::time::Duration::from_secs(30),
10923                Some("reader"),
10924            )
10925            .expect("schema builds")
10926            .sdl()
10927        };
10928
10929        let without = sdl_for(&withheld);
10930        assert!(
10931            !without.contains("add_to_score"),
10932            "not granted, so not there:\n{}",
10933            without
10934        );
10935        // And with no mutation of any kind left, there is no mutation root --
10936        // which is what `no mutations exist` is answered from.
10937        assert!(
10938            !without.contains("mutation: mutation_root"),
10939            "and no mutation root at all:\n{}",
10940            without
10941        );
10942
10943        let with = sdl_for(&granted);
10944        assert!(
10945            with.contains("add_to_score"),
10946            "named by a function permission, so it is:\n{}",
10947            with
10948        );
10949    }
10950
10951    /// An unrecognised role is a role that was granted nothing, not an error.
10952    #[test]
10953    fn a_role_the_document_never_names_is_granted_nothing() {
10954        let cache = Arc::new(create_test_schema_cache());
10955        let config = SchemaConfig {
10956            names: crate::names::NameOverrides::parse(
10957                r#"{"tables": {"public.users": {"permissions":
10958                     {"reader": {"select": {"columns": "*", "filter": {}}}}}}}"#,
10959            )
10960            .unwrap(),
10961            ..SchemaConfig::default()
10962        };
10963        let empty = build_ungranted_schema(&cache, &config).expect("it builds");
10964        let sdl = empty.sdl();
10965        assert!(
10966            sdl.contains("no_queries_available"),
10967            "nothing to read:\n{}",
10968            sdl
10969        );
10970        assert!(!sdl.contains("type users "), "and no table:\n{}", sdl);
10971        // A document with no permissions at all has no unrecognised roles.
10972        assert!(build_ungranted_schema(&cache, &SchemaConfig::default()).is_none());
10973    }
10974
10975    /// A column the role may write and not read is in the input and not in the
10976    /// type. The two column sets are the point: one schema cache, two answers.
10977    #[test]
10978    fn a_column_a_role_may_set_without_seeing_is_in_the_input_alone() {
10979        let cache = create_test_schema_cache();
10980        let names = crate::names::NameOverrides::parse(
10981            r#"{"tables": {"public.users": {"permissions":
10982                 {"user": {"select": {"columns": ["id"], "filter": {}},
10983                           "insert": {"columns": "*", "check": {}}}}}}}"#,
10984        )
10985        .unwrap();
10986        let view = crate::role::cache_for_role(&cache, &names, "user", false);
10987        let config = SchemaConfig {
10988            names: names.clone(),
10989            role: Some("user".to_string()),
10990            ..SchemaConfig::default()
10991        };
10992        let generated = build_schema(&view, &config);
10993        let object = &generated.object_types["users"];
10994        assert_eq!(
10995            object
10996                .fields
10997                .iter()
10998                .map(|f| f.name.as_str())
10999                .collect::<Vec<_>>(),
11000            vec!["id"],
11001            "the type shows what may be read"
11002        );
11003        assert!(
11004            object.writable_fields.iter().any(|f| f.name == "name"),
11005            "and the input keeps what may be written"
11006        );
11007
11008        let schema = build_dynamic_schema(
11009            &generated,
11010            &view,
11011            None,
11012            None,
11013            Arc::new(names),
11014            std::time::Duration::from_secs(30),
11015            Some("user"),
11016        )
11017        .expect("schema builds");
11018        let sdl = schema.sdl();
11019        let input = sdl
11020            .split("input users_insert_input")
11021            .nth(1)
11022            .and_then(|rest| rest.split('}').next())
11023            .expect("the insert input is registered");
11024        assert!(input.contains("name:"), "settable without being readable");
11025    }
11026
11027    /// A column PostgreSQL generates always is in the row type and in no
11028    /// write input. It is not a permission -- no role may name it -- so the
11029    /// refusal belongs to the schema rather than to the database.
11030    #[test]
11031    fn a_generated_column_is_readable_and_not_writable() {
11032        let mut cache = create_test_schema_cache();
11033        cache
11034            .tables
11035            .get_mut(&postrust_core::api_request::QualifiedIdentifier::new(
11036                "public", "users",
11037            ))
11038            .expect("the fixture has users")
11039            .columns
11040            .get_mut("id")
11041            .expect("with an id")
11042            .always_generated = true;
11043
11044        let names = crate::names::NameOverrides::default();
11045        let config = SchemaConfig {
11046            names: names.clone(),
11047            ..SchemaConfig::default()
11048        };
11049        let generated = build_schema(&cache, &config);
11050        let schema = build_dynamic_schema(
11051            &generated,
11052            &cache,
11053            None,
11054            None,
11055            Arc::new(names),
11056            std::time::Duration::from_secs(30),
11057            None,
11058        )
11059        .expect("schema builds");
11060        let sdl = schema.sdl();
11061
11062        let block = |header: &str| {
11063            sdl.split(header)
11064                .nth(1)
11065                .and_then(|rest| rest.split('}').next())
11066                .unwrap_or_else(|| panic!("{} is registered:\n{}", header, sdl))
11067                .to_string()
11068        };
11069        assert!(
11070            block("type users ").contains("id:"),
11071            "still readable:\n{}",
11072            sdl
11073        );
11074        for header in ["input users_insert_input", "input users_set_input"] {
11075            assert!(
11076                !block(header).contains("id:"),
11077                "{} may not name it:\n{}",
11078                header,
11079                block(header)
11080            );
11081        }
11082        // The table's only number was the generated column, so there is
11083        // nothing left to add to and no type for adding to it.
11084        assert!(
11085            !sdl.contains("users_inc_input"),
11086            "and nothing is left to increment:\n{}",
11087            sdl
11088        );
11089        assert!(
11090            block("input users_insert_input").contains("name:"),
11091            "and the rest of the table is untouched",
11092        );
11093    }
11094
11095    #[test]
11096    fn test_create_object_type() {
11097        let table = create_test_table("users");
11098        let obj = TableObjectType::from_table(&table);
11099        let _gql_obj = create_object_type(&obj, &[], None);
11100    }
11101
11102    #[test]
11103    fn test_create_query_type() {
11104        let cache = create_test_schema_cache();
11105        let config = SchemaConfig::default();
11106        let generated = build_schema(&cache, &config);
11107
11108        let _query = create_query_type(
11109            &generated,
11110            "query_root",
11111            None,
11112            Arc::new(HashMap::new()),
11113            Arc::new(Default::default()),
11114        );
11115    }
11116
11117    #[test]
11118    fn test_create_mutation_type() {
11119        let cache = create_test_schema_cache();
11120        let config = SchemaConfig::default();
11121        let generated = build_schema(&cache, &config);
11122
11123        let _mutation = create_mutation_type(
11124            &generated,
11125            "mutation_root",
11126            Arc::new(HashMap::new()),
11127            Arc::new(HashMap::new()),
11128            Arc::new(Default::default()),
11129            None,
11130            None,
11131        );
11132    }
11133
11134    // ============================================================================
11135    // Scalar Tests
11136    // ============================================================================
11137
11138    #[test]
11139    fn a_scalar_is_named_the_way_a_client_declares_it() {
11140        use crate::types::{pg_type_to_graphql, GraphQLType};
11141
11142        // These names appear in the client's own queries -- `query ($x:
11143        // jsonb!)` names a type that has to exist under exactly that
11144        // spelling -- so they are asserted rather than left to the Display
11145        // impl.
11146        assert_eq!(pg_type_to_graphql("jsonb").to_string(), "jsonb");
11147        assert_eq!(pg_type_to_graphql("json").to_string(), "json");
11148        assert_eq!(pg_type_to_graphql("int8").to_string(), "bigint");
11149        assert_eq!(pg_type_to_graphql("numeric").to_string(), "numeric");
11150        assert_eq!(pg_type_to_graphql("timestamptz").to_string(), "timestamptz");
11151        assert_eq!(pg_type_to_graphql("timestamp").to_string(), "timestamp");
11152        assert_eq!(pg_type_to_graphql("uuid").to_string(), "uuid");
11153
11154        // A type this server knows nothing about keeps its own name, which is
11155        // how a PostGIS column and a database enum both become usable.
11156        assert_eq!(pg_type_to_graphql("geometry").to_string(), "geometry");
11157        assert_eq!(
11158            pg_type_to_graphql("colors_enum"),
11159            GraphQLType::Custom("colors_enum".to_string())
11160        );
11161
11162        // And the leaf of an array is what gets registered, not the array.
11163        assert_eq!(
11164            leaf_scalar_name(&pg_type_to_graphql("_text")),
11165            "String".to_string()
11166        );
11167    }
11168
11169    // ============================================================================
11170    // Filter Input Type Tests
11171    // ============================================================================
11172
11173    #[test]
11174    fn every_table_gets_a_boolean_expression() {
11175        let cache = create_test_schema_cache();
11176        let config = SchemaConfig::default();
11177        let generated = build_schema(&cache, &config);
11178
11179        let (inputs, _) = crate::input::bool_exp::build_inputs(
11180            &generated.object_types,
11181            &generated.relationship_fields,
11182        );
11183        let names: HashSet<String> = inputs.iter().map(|i| i.type_name().to_string()).collect();
11184
11185        for table in generated.object_types.keys() {
11186            let expected = format!("{}_bool_exp", table);
11187            assert!(
11188                names.contains(&expected),
11189                "no {} among {:?}",
11190                expected,
11191                names
11192            );
11193        }
11194        // The scalars the fixture's columns use.
11195        assert!(names.contains("String_comparison_exp"));
11196        assert!(names.contains("Int_comparison_exp"));
11197    }
11198
11199    #[test]
11200    fn a_schema_carrying_the_boolean_expressions_still_builds() {
11201        let cache = create_test_schema_cache();
11202        let config = SchemaConfig::default();
11203        let generated = build_schema(&cache, &config);
11204
11205        let schema = build_dynamic_schema(
11206            &generated,
11207            &cache,
11208            None,
11209            None,
11210            Arc::new(Default::default()),
11211            std::time::Duration::from_secs(30),
11212            None,
11213        );
11214        assert!(schema.is_ok(), "{:?}", schema.err());
11215
11216        let sdl = schema.unwrap().sdl();
11217        assert!(
11218            sdl.contains("_bool_exp"),
11219            "no boolean expressions in:\n{}",
11220            sdl
11221        );
11222        assert!(sdl.contains("_and"), "no _and in the generated expressions");
11223    }
11224
11225    // ============================================================================
11226    // Subscription Tests
11227    // ============================================================================
11228
11229    #[test]
11230    fn test_build_schema_with_subscriptions() {
11231        let cache = create_test_schema_cache();
11232        let config = SchemaConfig {
11233            enable_subscriptions: true,
11234            ..SchemaConfig::default()
11235        };
11236        let generated = build_schema(&cache, &config);
11237
11238        // Generate subscription fields
11239        let sub_fields = generate_subscription_fields(&cache, &generated);
11240        assert!(!sub_fields.is_empty(), "Should have subscription fields");
11241
11242        // Build schema with subscriptions
11243        let result = build_dynamic_schema(
11244            &generated,
11245            &cache,
11246            Some(&sub_fields),
11247            None,
11248            Arc::new(Default::default()),
11249            std::time::Duration::from_secs(30),
11250            None,
11251        );
11252        assert!(result.is_ok(), "Schema with subscriptions should build");
11253    }
11254
11255    #[test]
11256    fn test_subscription_field_generation() {
11257        let cache = create_test_schema_cache();
11258        let config = SchemaConfig::default();
11259        let generated = build_schema(&cache, &config);
11260
11261        let fields = generate_subscription_fields(&cache, &generated);
11262
11263        // Should have one subscription field for the users table
11264        assert_eq!(fields.len(), 1);
11265        assert_eq!(fields[0].name, "users");
11266        assert_eq!(fields[0].table_name, "users");
11267        assert_eq!(fields[0].channel_name(), "postrust_public_users");
11268    }
11269
11270    #[test]
11271    fn test_create_subscription_type() {
11272        let cache = create_test_schema_cache();
11273        let config = SchemaConfig::default();
11274        let generated = build_schema(&cache, &config);
11275        // A live query per queryable root: the subscription root mirrors the
11276        // query root, which is the whole contract.
11277        let _subscription = create_subscription_type(
11278            &generated,
11279            "subscription_root",
11280            None,
11281            Arc::new(Default::default()),
11282            Arc::new(Default::default()),
11283            std::time::Duration::from_secs(30),
11284        );
11285    }
11286}
11287
11288/// One step of a JSON path: a key of an object or an index of an array.
11289#[derive(Debug, PartialEq, Eq)]
11290enum PathStep {
11291    Key(String),
11292    Index(usize),
11293}
11294
11295/// Read one part of a document, by the path spelling Hasura accepts.
11296///
11297/// `column(path: "objs[0]['你好']")` -- an optional leading `$`, then keys
11298/// written bare or after a dot, and indices or quoted keys written in
11299/// brackets. A step that names something the document does not have is null,
11300/// which is what `#>` answers to the same path.
11301fn walk_json_path(value: &Value, path: &str) -> Result<Value, async_graphql::Error> {
11302    let mut at = value;
11303    for step in parse_json_path(path)? {
11304        let next = match (&step, at) {
11305            (PathStep::Key(key), Value::Object(map)) => map.get(key.as_str()),
11306            (PathStep::Index(index), Value::List(items)) => items.get(*index),
11307            // An index of an object or a key of an array reads nothing, and so
11308            // does anything asked of a scalar.
11309            _ => None,
11310        };
11311        match next {
11312            Some(next) => at = next,
11313            None => return Ok(Value::Null),
11314        }
11315    }
11316    Ok(at.clone())
11317}
11318
11319/// A path whose bracket never closes: written wrong rather than written oddly.
11320fn unclosed_bracket(path: &str) -> async_graphql::Error {
11321    let message = format!("\"{}\" is not a JSON path: a bracket is never closed", path);
11322    validation_error(&message)
11323}
11324
11325/// Parse the path spelling Hasura accepts into its steps.
11326///
11327/// Deliberately tolerant about separators, because the corpus writes the same
11328/// path four ways: `objs[0]["x"]`, `objs[0].["x"]`, `objs.[0].["x"]` and
11329/// `.objs[0]['x']` all name one thing. What is not tolerated is a bracket that
11330/// never closes, which is a path the client got wrong rather than a spelling.
11331fn parse_json_path(path: &str) -> Result<Vec<PathStep>, async_graphql::Error> {
11332    let mut steps = Vec::new();
11333    let mut chars = path.chars().peekable();
11334    // `$` is the document itself, and naming it is optional.
11335    if chars.peek() == Some(&'$') {
11336        chars.next();
11337    }
11338    while let Some(&c) = chars.peek() {
11339        match c {
11340            // A separator carries no step of its own.
11341            '.' => {
11342                chars.next();
11343            }
11344            '[' => {
11345                chars.next();
11346                let quote = match chars.peek() {
11347                    Some(&q @ ('\'' | '"')) => {
11348                        chars.next();
11349                        Some(q)
11350                    }
11351                    _ => None,
11352                };
11353                let mut inner = String::new();
11354                loop {
11355                    let Some(c) = chars.next() else {
11356                        return Err(unclosed_bracket(path));
11357                    };
11358                    match (quote, c) {
11359                        (Some(quote), c) if c == quote => break,
11360                        (None, ']') => break,
11361                        _ => inner.push(c),
11362                    }
11363                }
11364                // A quoted key's closing bracket, which the quote ended before.
11365                if quote.is_some() {
11366                    match chars.next() {
11367                        Some(']') => {}
11368                        _ => return Err(unclosed_bracket(path)),
11369                    }
11370                }
11371                // An unquoted number is an index; anything else is a key,
11372                // since an object may be keyed by a word as well as a digit.
11373                match (quote, inner.parse::<usize>()) {
11374                    (None, Ok(index)) => steps.push(PathStep::Index(index)),
11375                    _ => steps.push(PathStep::Key(inner)),
11376                }
11377            }
11378            _ => {
11379                let mut key = String::new();
11380                while let Some(&c) = chars.peek() {
11381                    if matches!(c, '.' | '[') {
11382                        break;
11383                    }
11384                    key.push(c);
11385                    chars.next();
11386                }
11387                steps.push(PathStep::Key(key));
11388            }
11389        }
11390    }
11391    Ok(steps)
11392}
11393
11394#[cfg(test)]
11395mod json_path_tests {
11396    use super::*;
11397
11398    fn steps(path: &str) -> Vec<PathStep> {
11399        parse_json_path(path).expect("the path parses")
11400    }
11401
11402    fn key(name: &str) -> PathStep {
11403        PathStep::Key(name.to_string())
11404    }
11405
11406    #[test]
11407    fn the_document_itself_has_no_steps() {
11408        assert_eq!(steps("$"), vec![]);
11409        assert_eq!(steps(""), vec![]);
11410    }
11411
11412    #[test]
11413    fn a_key_is_written_bare_or_after_a_dot() {
11414        assert_eq!(steps("a"), vec![key("a")]);
11415        assert_eq!(steps(".obj.c1"), vec![key("obj"), key("c1")]);
11416        assert_eq!(steps("._underscore"), vec![key("_underscore")]);
11417    }
11418
11419    #[test]
11420    fn a_bracket_holds_an_index_or_a_quoted_key() {
11421        assert_eq!(steps("arr[0]"), vec![key("arr"), PathStep::Index(0)]);
11422        assert_eq!(steps("['!@#$%^']"), vec![key("!@#$%^")]);
11423        assert_eq!(
11424            steps("translations['hello world!']"),
11425            vec![key("translations"), key("hello world!")]
11426        );
11427    }
11428
11429    /// The corpus writes one path four ways, and Hasura reads them all alike.
11430    #[test]
11431    fn a_dot_before_a_bracket_changes_nothing() {
11432        let expected = vec![key("objs"), PathStep::Index(0), key("\u{4f60}\u{597d}")];
11433        for spelling in [
11434            "objs[0]['\u{4f60}\u{597d}']",
11435            "objs[0][\"\u{4f60}\u{597d}\"]",
11436            "objs[0].[\"\u{4f60}\u{597d}\"]",
11437            "objs.[0].[\"\u{4f60}\u{597d}\"]",
11438        ] {
11439            assert_eq!(steps(spelling), expected, "{}", spelling);
11440        }
11441    }
11442
11443    #[test]
11444    fn a_bracket_that_never_closes_is_refused() {
11445        assert!(parse_json_path("arr[0").is_err());
11446        assert!(parse_json_path("['key'").is_err());
11447    }
11448
11449    #[test]
11450    fn a_step_the_document_does_not_have_reads_null() {
11451        let document = Value::from_json(serde_json::json!({
11452            "obj": {"c1": "c2"},
11453            "arr": [1, 2, 3],
11454        }))
11455        .expect("a document");
11456        let at = |path| walk_json_path(&document, path).expect("the path parses");
11457        assert_eq!(at(".obj.c1"), Value::String("c2".to_string()));
11458        assert_eq!(at("arr[1]"), Value::Number(2.into()));
11459        assert_eq!(at("arr[9]"), Value::Null);
11460        assert_eq!(at(".obj.nothing"), Value::Null);
11461        // An index of an object, and a key of an array: neither reads
11462        // anything, which is what `#>` answers.
11463        assert_eq!(at("obj[0]"), Value::Null);
11464        assert_eq!(at("arr.first"), Value::Null);
11465    }
11466}