Skip to main content

postrust_graphql/
handler.rs

1//! Axum handler for the /graphql endpoint.
2//!
3//! Provides GraphQL request handling using async-graphql with dynamic schema
4//! generation from the PostgreSQL schema cache.
5
6use crate::context::GraphQLContext;
7use crate::error::GraphQLError;
8use crate::schema::object::TableObjectType;
9use crate::schema::relationship::RelationshipField;
10use crate::schema::{build_schema, FederationEntity, GeneratedSchema, MutationType, SchemaConfig};
11use crate::subscription::{
12    generate_subscription_fields, NotifyBroker, SubscriptionField as SubField,
13};
14use async_graphql::dynamic::*;
15use async_graphql::extensions::{
16    Extension, ExtensionContext, ExtensionFactory, NextResolve, ResolveInfo,
17};
18use async_graphql::parser::types::{FragmentDefinition, Selection, SelectionSet, TypeCondition};
19use async_graphql::{Name, Positioned, SelectionField, ServerResult, Value};
20use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
21use axum::extract::State;
22use axum::response::IntoResponse;
23use postrust_core::schema_cache::SchemaCache;
24use sqlx::PgPool;
25use std::collections::{BTreeMap, HashMap, HashSet};
26use std::sync::Arc;
27use tokio::sync::RwLock;
28use tracing::{debug, info, trace};
29
30/// GraphQL execution state shared across requests.
31pub struct GraphQLState {
32    /// Database connection pool
33    pub pool: PgPool,
34    /// Schema cache
35    pub schema_cache: Arc<SchemaCache>,
36    /// Generated GraphQL schema
37    pub generated_schema: GeneratedSchema,
38    /// async-graphql Schema (built dynamically)
39    ///
40    /// The unrestricted one: what an administrator is answered from, and what
41    /// every caller is answered from on a server with no permission document.
42    pub schema: Schema,
43    /// One schema per role the permission document names.
44    ///
45    /// Hasura builds a schema per role because a permission is a statement
46    /// about what exists, not about what is allowed: a role with no `select`
47    /// on a table has no field to name, and naming one is a validation failure
48    /// rather than a denial. Each is built from a schema cache already reduced
49    /// to what that role may see.
50    ///
51    /// Built at startup rather than on first use, so a permission document
52    /// that cannot be built fails while someone is watching rather than on the
53    /// first request from whichever role it broke.
54    ///
55    /// Keyed by the role and by whether the caller asked for what only a
56    /// backend may name -- which is a second schema rather than a check,
57    /// because `backend_only` decides whether a field exists.
58    pub role_schemas: HashMap<(String, bool), Schema>,
59    /// What a role the document never names is answered from.
60    ///
61    /// Not a refusal. To Hasura an unrecognised role is a role with nothing
62    /// granted, so it gets a schema with nothing in it -- and a request is
63    /// answered `field 'user' not found in type: 'query_root'`, or `no
64    /// mutations exist` where there is no mutation root at all, rather than
65    /// with a complaint about the role. `address_permission_error` sends
66    /// `X-Hasura-Role: merchant`, which no permission mentions, and expects
67    /// exactly that.
68    ///
69    /// One schema for every such role rather than one each: they are all the
70    /// same schema, since what they were granted is the same nothing.
71    pub ungranted_schema: Option<Schema>,
72    /// Schema configuration
73    pub config: SchemaConfig,
74    /// Subscription fields
75    pub subscription_fields: Vec<SubField>,
76    /// Notification broker for subscriptions
77    pub broker: Arc<RwLock<Option<NotifyBroker>>>,
78}
79
80/// async-graphql's dynamic resolver requires every union value to carry a
81/// concrete type, including `null`. A typed null is then treated as an object,
82/// though, so its non-null fields fail. Convert only the library's error for a
83/// bare null `_Entity` list member back into the nullable member the schema
84/// declares.
85struct NullableFederationEntity;
86
87impl ExtensionFactory for NullableFederationEntity {
88    fn create(&self) -> Arc<dyn Extension> {
89        Arc::new(Self)
90    }
91}
92
93#[async_graphql::async_trait::async_trait]
94impl Extension for NullableFederationEntity {
95    async fn resolve(
96        &self,
97        ctx: &ExtensionContext<'_>,
98        info: ResolveInfo<'_>,
99        next: NextResolve<'_>,
100    ) -> ServerResult<Option<Value>> {
101        // IMPORTANT: async-graphql 7.0.17 invokes this hook for every field and
102        // every list member, not only `_entities`. That leaves one boxed
103        // extension future per call, so do not copy this pattern for other
104        // field-specific behaviour. Its dynamic API cannot represent a bare
105        // nullable union member: an untyped null is rejected and a typed null
106        // is resolved as an object. Until that changes upstream, keep the
107        // successful path to the one required `next.run`; in particular,
108        // defer all string comparisons until the union error has occurred.
109        let parent_type = info.parent_type;
110        let return_type = info.return_type;
111        match next.run(ctx, info).await {
112            Err(error)
113                if error.message
114                    == "internal: invalid value for union \"_Entity\", expected \"FieldValue::WithType\""
115                    && parent_type == "[_Entity]"
116                    && return_type == "_Entity" =>
117            {
118                Ok(Some(Value::Null))
119            }
120            result => result,
121        }
122    }
123}
124
125/// Build one schema for each role the permission document names.
126///
127/// Each is built from a cache reduced to what that role may see, so nothing in
128/// the builders needs to know a permission exists -- a table this role cannot
129/// read is one the code generating root fields cannot see either. See
130/// [`crate::role`] for why the filtering is done there rather than threaded
131/// through.
132///
133/// Empty when the document carries no permissions, which is what leaves an
134/// unconfigured server building exactly the one schema it always did.
135fn build_role_schemas(
136    schema_cache: &Arc<SchemaCache>,
137    config: &SchemaConfig,
138) -> Result<HashMap<(String, bool), Schema>, GraphQLError> {
139    if !config.names.has_permissions() {
140        return Ok(HashMap::new());
141    }
142
143    let mut schemas = HashMap::new();
144    for role in config.names.roles() {
145        // An administrator is not a role with permissions; it is the caller
146        // the permissions do not apply to. Building it a restricted schema
147        // would take the bypass away.
148        if role == postrust_auth::hasura::ADMIN_ROLE {
149            continue;
150        }
151
152        // A second schema only where the role has something a backend alone
153        // may name. Otherwise the one schema answers both, since there is
154        // nothing for the flag to hide.
155        let variants: &[bool] = match crate::role::has_backend_only(&config.names, role) {
156            true => &[false, true],
157            false => &[false],
158        };
159
160        for &backend in variants {
161            let view = Arc::new(crate::role::cache_for_role(
162                schema_cache,
163                &config.names,
164                role,
165                backend,
166            ));
167            let mut role_config = config.clone();
168            role_config.role = Some(role.to_string());
169
170            let generated = build_schema(&view, &role_config);
171            // Subscriptions are left to the unrestricted schema for now: a live
172            // query is one this server answers from notifications rather than from
173            // the query root, so restricting it is a different piece of work from
174            // restricting a read.
175            let built = build_dynamic_schema(
176                &generated,
177                &view,
178                None,
179                role_config.max_rows,
180                Arc::new(role_config.names.clone()),
181                role_config.subscription_refresh(),
182                Some(role),
183            );
184
185            match built {
186                Ok(schema) => {
187                    schemas.insert((role.to_string(), backend), schema);
188                }
189                // One role's permissions being unbuildable is not a reason for
190                // every other role to lose its API, which is what returning here
191                // would mean -- the caller serves no GraphQL at all on an error.
192                // The role is left without a schema instead, which the request
193                // path already reads as a refusal: it fails closed, loudly, and
194                // alone.
195                Err(e) => tracing::error!(
196                    "GraphQL schema for role \"{}\" could not be built, \
197                 so that role is refused: {}",
198                    role,
199                    e
200                ),
201            }
202        }
203    }
204
205    tracing::info!("GraphQL schemas built for {} roles", schemas.len());
206    Ok(schemas)
207}
208
209/// The schema a role the document never names is answered from.
210///
211/// Built the same way every other role's is, from a cache reduced by a role
212/// nothing was granted to -- which leaves no tables, so it is the same schema
213/// whatever the unrecognised role is called. `None` where there is no
214/// permission document, since then no role is unrecognised.
215fn build_ungranted_schema(
216    schema_cache: &Arc<SchemaCache>,
217    config: &SchemaConfig,
218) -> Option<Schema> {
219    if !config.names.has_permissions() {
220        return None;
221    }
222    // A name no permission can carry: `roles()` reads them out of the
223    // document, and this one is not a role anybody wrote down.
224    const NOBODY: &str = "\u{0}ungranted";
225    let view = Arc::new(crate::role::cache_for_role(
226        schema_cache,
227        &config.names,
228        NOBODY,
229        false,
230    ));
231    let mut role_config = config.clone();
232    role_config.role = Some(NOBODY.to_string());
233    let generated = build_schema(&view, &role_config);
234    build_dynamic_schema(
235        &generated,
236        &view,
237        None,
238        role_config.max_rows,
239        Arc::new(role_config.names.clone()),
240        role_config.subscription_refresh(),
241        Some(NOBODY),
242    )
243    .map_err(|e| {
244        tracing::error!(
245            "the schema for a role with nothing granted could not be built,              so such a role is refused instead: {}",
246            e
247        )
248    })
249    .ok()
250}
251
252impl GraphQLState {
253    /// Create new GraphQL state from schema cache.
254    pub fn new(
255        pool: PgPool,
256        schema_cache: Arc<SchemaCache>,
257        config: SchemaConfig,
258    ) -> Result<Self, GraphQLError> {
259        let generated_schema = build_schema(&schema_cache, &config);
260        let subscription_fields = if config.enable_subscriptions {
261            generate_subscription_fields(&schema_cache, &generated_schema)
262        } else {
263            Vec::new()
264        };
265        let schema = build_dynamic_schema(
266            &generated_schema,
267            &schema_cache,
268            if config.enable_subscriptions {
269                Some(subscription_fields.as_slice())
270            } else {
271                None
272            },
273            config.max_rows,
274            Arc::new(config.names.clone()),
275            config.subscription_refresh(),
276            None,
277        )?;
278
279        let role_schemas = build_role_schemas(&schema_cache, &config)?;
280        let ungranted_schema = build_ungranted_schema(&schema_cache, &config);
281
282        Ok(Self {
283            pool: pool.clone(),
284            schema_cache,
285            generated_schema,
286            schema,
287            role_schemas,
288            ungranted_schema,
289            config,
290            subscription_fields,
291            broker: Arc::new(RwLock::new(None)),
292        })
293    }
294
295    /// Which schema answers for a caller speaking as this role.
296    ///
297    /// A role the document does not name is answered from the schema with
298    /// nothing in it, which is Hasura's reading: an unrecognised role is a
299    /// role that was granted nothing, not an error. Three things are
300    /// deliberately not that: a server with no permission document answers
301    /// everyone from the unrestricted schema, an administrator is answered
302    /// from it too -- permissions are rules for everyone else -- and so is a
303    /// request on a server where the layer is off.
304    ///
305    /// `None` only where the empty schema itself could not be built, which the
306    /// caller still turns into a refusal.
307    pub fn schema_for(&self, role: Option<&str>, backend: bool) -> Option<&Schema> {
308        if !self.config.names.has_permissions() {
309            return Some(&self.schema);
310        }
311        match role {
312            None | Some(postrust_auth::hasura::ADMIN_ROLE) => Some(&self.schema),
313            Some(role) => self
314                .role_schemas
315                .get(&(role.to_string(), backend))
316                // A role with nothing backend-only has one schema, under
317                // `false`, and it answers a backend caller too.
318                .or_else(|| self.role_schemas.get(&(role.to_string(), false)))
319                .or(self.ungranted_schema.as_ref()),
320        }
321    }
322
323    /// Rebuild the schema (e.g., after schema cache refresh).
324    #[allow(clippy::needless_update)]
325    pub fn rebuild(&mut self) -> Result<(), GraphQLError> {
326        self.role_schemas = build_role_schemas(&self.schema_cache, &self.config)?;
327        self.ungranted_schema = build_ungranted_schema(&self.schema_cache, &self.config);
328        self.generated_schema = build_schema(&self.schema_cache, &self.config);
329        self.subscription_fields = if self.config.enable_subscriptions {
330            generate_subscription_fields(&self.schema_cache, &self.generated_schema)
331        } else {
332            Vec::new()
333        };
334        self.schema = build_dynamic_schema(
335            &self.generated_schema,
336            &self.schema_cache,
337            if self.config.enable_subscriptions {
338                Some(self.subscription_fields.as_slice())
339            } else {
340                None
341            },
342            self.config.max_rows,
343            Arc::new(self.config.names.clone()),
344            self.config.subscription_refresh(),
345            None,
346        )?;
347        Ok(())
348    }
349
350    /// Initialize the subscription broker.
351    ///
352    /// This should be called after creating the state to enable subscriptions.
353    pub async fn init_subscriptions(&self) -> Result<(), crate::subscription::BrokerError> {
354        if !self.config.enable_subscriptions {
355            return Ok(());
356        }
357
358        let broker = NotifyBroker::new(self.pool.clone());
359
360        // Collect all channels to listen on
361        let channels: Vec<String> = self
362            .subscription_fields
363            .iter()
364            .map(|f| f.channel_name())
365            .collect();
366
367        if !channels.is_empty() {
368            broker.start(channels).await?;
369            info!(
370                "Subscription broker started with {} channels",
371                self.subscription_fields.len()
372            );
373        }
374
375        // Store the broker
376        let mut broker_guard = self.broker.write().await;
377        *broker_guard = Some(broker);
378
379        Ok(())
380    }
381
382    /// Stop the subscription broker.
383    pub async fn stop_subscriptions(&self) {
384        let broker_guard = self.broker.read().await;
385        if let Some(broker) = broker_guard.as_ref() {
386            broker.stop().await;
387        }
388    }
389
390    /// Get the notification broker.
391    pub async fn get_broker(&self) -> Option<Arc<RwLock<Option<NotifyBroker>>>> {
392        Some(Arc::clone(&self.broker))
393    }
394}
395
396/// Handle a GraphQL request.
397pub async fn graphql_handler(
398    State(state): State<Arc<GraphQLState>>,
399    ctx: GraphQLContext,
400    req: GraphQLRequest,
401) -> GraphQLResponse {
402    let request = req
403        .into_inner()
404        .data(ctx)
405        .data(state.pool.clone())
406        .data(Arc::clone(&state.broker));
407    state.schema.execute(request).await.into()
408}
409
410/// Handle GraphQL WebSocket subscription upgrade.
411///
412/// This should be called with a WebSocket upgrade request to enable
413/// GraphQL subscriptions over WebSocket.
414pub async fn graphql_ws_handler(
415    State(state): State<Arc<GraphQLState>>,
416    protocol: async_graphql_axum::GraphQLProtocol,
417    ws: axum::extract::WebSocketUpgrade,
418) -> impl IntoResponse {
419    let schema = state.schema.clone();
420    let pool = state.pool.clone();
421    let broker = Arc::clone(&state.broker);
422
423    ws.protocols(["graphql-transport-ws", "graphql-ws"])
424        .on_upgrade(move |socket| async move {
425            let mut data = async_graphql::Data::default();
426            data.insert(pool);
427            data.insert(broker);
428
429            async_graphql_axum::GraphQLWebSocket::new(socket, schema, protocol)
430                .with_data(data)
431                .serve()
432                .await
433        })
434}
435
436/// Handle GraphQL playground request.
437pub async fn graphql_playground() -> impl axum::response::IntoResponse {
438    axum::response::Html(async_graphql::http::playground_source(
439        async_graphql::http::GraphQLPlaygroundConfig::new("/graphql")
440            .subscription_endpoint("/graphql/ws"),
441    ))
442}
443
444/// The column a field name refers to.
445///
446/// The name itself, unless the table exposes that column under another one.
447/// Every path from a request to SQL goes through here: a renamed column is a
448/// name in the schema and in nothing else, so the translation has to happen at
449/// each boundary rather than once.
450fn column_for<'a>(
451    names: &'a crate::names::NameOverrides,
452    schema: &str,
453    table: &str,
454    field: &'a str,
455) -> &'a str {
456    names.column_source(schema, table, field).unwrap_or(field)
457}
458
459/// The same, given the table itself.
460fn table_column_for<'a>(
461    names: &'a crate::names::NameOverrides,
462    table: &postrust_core::schema_cache::Table,
463    field: &'a str,
464) -> &'a str {
465    column_for(names, &table.schema, &table.name, field)
466}
467
468/// The select list that renames a table's columns to the fields they are
469/// exposed as, or `None` where nothing is renamed.
470///
471/// Applied to a projection over the table rather than to the table itself, so
472/// the row a computed field or a computed relationship is passed stays the
473/// table's own composite -- `cannot cast type record to author` is what
474/// happens when it does not.
475fn rename_projection(
476    table: &postrust_core::schema_cache::Table,
477    alias: &str,
478    names: &crate::names::NameOverrides,
479) -> Option<String> {
480    if !names.renames_columns(&table.schema, &table.name) {
481        return None;
482    }
483    let parts: Vec<String> = table
484        .columns
485        .values()
486        .map(|column| {
487            let exposed = names
488                .column(&table.schema, &table.name, &column.name)
489                .unwrap_or(&column.name);
490            format!(
491                "{}.{} AS {}",
492                postrust_sql::escape_ident(alias),
493                postrust_sql::escape_ident(&column.name),
494                postrust_sql::escape_ident(exposed)
495            )
496        })
497        .collect();
498    match parts.is_empty() {
499        true => None,
500        false => Some(parts.join(", ")),
501    }
502}
503
504/// A table's column types, keyed by the names those columns are exposed under.
505///
506/// [`row_json`] names the shape columns it has to rewrite as GeoJSON, and it
507/// names them in whatever the projection called them. Over a renamed
508/// projection that is the field name, not the column's.
509fn exposed_column_types(
510    table: &postrust_core::schema_cache::Table,
511    names: &crate::names::NameOverrides,
512) -> HashMap<String, String> {
513    table
514        .columns
515        .values()
516        .map(|column| {
517            let exposed = names
518                .column(&table.schema, &table.name, &column.name)
519                .unwrap_or(&column.name);
520            (exposed.to_string(), column.nominal_type.clone())
521        })
522        .collect()
523}
524
525/// The type a value is written as, which keeps the list an array column is.
526///
527/// Not the leaf scalar: `c34_text_array` is a `text[]`, and a client sending
528/// `$textArray: [String]` into it was refused for offering a list where the
529/// input said `String`. Every write is optional, so nothing here is non-null.
530fn write_type_ref(graphql_type: &crate::types::GraphQLType) -> TypeRef {
531    match graphql_type {
532        crate::types::GraphQLType::List(inner) => TypeRef::named_list(leaf_scalar_name(inner)),
533        other => TypeRef::named(leaf_scalar_name(other)),
534    }
535}
536
537/// The scalar at the bottom of a type, past any list wrapping.
538fn leaf_scalar_name(graphql_type: &crate::types::GraphQLType) -> String {
539    match graphql_type {
540        crate::types::GraphQLType::List(inner) => leaf_scalar_name(inner),
541        other => other.to_string(),
542    }
543}
544
545/// Build the dynamic async-graphql schema from our generated schema.
546fn build_dynamic_schema(
547    generated: &GeneratedSchema,
548    _schema_cache: &SchemaCache,
549    subscription_fields: Option<&[SubField]>,
550    max_rows: Option<i64>,
551    names: Arc<crate::names::NameOverrides>,
552    subscription_refresh: std::time::Duration,
553    // Whose schema this is, where it is one role's. What it decides here is
554    // which columns a write may name, which is not the same question as which
555    // columns may be read -- Hasura grants the two separately, and a role that
556    // may set a column it cannot read is ordinary rather than exotic.
557    role: Option<&str>,
558) -> Result<Schema, GraphQLError> {
559    // Create object types for each table
560    let mut object_types: HashMap<String, Object> = HashMap::new();
561
562    // A table with no readable field gets no type at all. That is not an
563    // empty table -- it is one this role may write and not read, and a GraphQL
564    // object type with no fields is not a legal type. Everything that would
565    // have returned it is left out with it.
566    for (type_name, obj) in &generated.object_types {
567        if obj.fields.is_empty() {
568            continue;
569        }
570        let relationships = generated
571            .relationship_fields
572            .get(type_name)
573            .map(|r| r.as_slice())
574            .unwrap_or(&[]);
575        let table_obj = create_object_type(
576            obj,
577            relationships,
578            generated.federation_entities.get(type_name),
579        );
580        object_types.insert(type_name.clone(), table_obj);
581    }
582
583    // Aggregate types: every readable table gets them, because every table has
584    // a count even when it has nothing to sum -- and so does a table a role may
585    // count and not read, which is the whole of what such a role was granted.
586    // Read from the roots that were generated rather than worked out again,
587    // for the reason the write inputs are: two answers to one question drift.
588    let countable: HashSet<&str> = generated
589        .query_fields
590        .iter()
591        .filter(|field| field.aggregates && !field.is_by_pk)
592        .map(|field| field.type_name.as_str())
593        .collect();
594    for (type_name, obj) in &generated.object_types {
595        if obj.fields.is_empty() && !countable.contains(type_name.as_str()) {
596            continue;
597        }
598        for aggregate_type in create_aggregate_types(&obj.local_name, obj) {
599            object_types.insert(aggregate_type.type_name().to_string(), aggregate_type);
600        }
601    }
602
603    // One mutation response per table that has any mutation, and only those:
604    // an unreferenced type is still a type, and a read-only view would
605    // otherwise contribute a response nothing can return.
606    let mutable: HashMap<String, String> = generated
607        .mutation_fields
608        .iter()
609        .filter_map(|f| {
610            // The table's own name. A bulk write already answers with the
611            // response type, and trimming the suffix here is what keeps
612            // `<t>_mutation_response_mutation_response` from being built: the
613            // set used to be carried by `insert_one` and `update_by_pk`
614            // naming the bare type, so a table with only bulk writes -- which
615            // is what a role that cannot read one has -- registered the wrong
616            // name and left the right one missing.
617            let response = f
618                .return_type
619                .trim_matches(|c| c == '[' || c == ']' || c == '!')
620                .strip_suffix("_mutation_response")?;
621            Some((response.to_string(), f.type_name.clone()))
622        })
623        .collect();
624    for (base_name, row_type_name) in mutable {
625        // Whether it has rows to give back. A write to a table this role
626        // cannot read answers with `affected_rows` and nothing else -- there
627        // is no row type for `returning` to be a list of.
628        let returning = generated
629            .object_types
630            .get(&row_type_name)
631            .is_some_and(|object| !object.fields.is_empty());
632        object_types.insert(
633            mutation_response_type_name(&base_name),
634            create_mutation_response_type(&base_name, &row_type_name, returning),
635        );
636    }
637
638    // Non-federated Hasura-compatible schemas use `query_root`, not `Query`.
639    // Federation SDL from async-graphql does not include an operation-root
640    // schema definition, so Apollo composition treats only the conventional
641    // root names as root types.
642    let query_root = if generated.enable_federation {
643        "Query"
644    } else {
645        "query_root"
646    };
647    let mutation_root = if generated.enable_federation {
648        "Mutation"
649    } else {
650        "mutation_root"
651    };
652    let subscription_root = if generated.enable_federation {
653        "Subscription"
654    } else {
655        "subscription_root"
656    };
657
658    // Create query type. Resolvers need the relationship map to embed related
659    // rows, so it is shared into each closure.
660    let relationships = Arc::new(generated.relationship_fields.clone());
661    // (schema, table) -> GraphQL type name. A nested insert is handed a table
662    // by a relationship and has to find that table's relationships in turn,
663    // and the map they live in is keyed by type name -- which is not always
664    // the table's name, since a second schema prefixes it and a name may have
665    // been given.
666    let type_names: Arc<HashMap<(String, String), String>> = Arc::new(
667        generated
668            .object_types
669            .iter()
670            .map(|(type_name, object)| {
671                (
672                    (object.table.schema.clone(), object.table.name.clone()),
673                    type_name.clone(),
674                )
675            })
676            .collect(),
677    );
678    let query = add_function_fields(
679        create_query_type(
680            generated,
681            query_root,
682            max_rows,
683            Arc::clone(&relationships),
684            Arc::clone(&names),
685        ),
686        generated,
687        false,
688        max_rows,
689        Arc::clone(&relationships),
690        Arc::clone(&names),
691    );
692
693    // Create mutation type
694    let mutation = if !generated.mutation_fields.is_empty()
695        || generated.function_fields.iter().any(|f| f.volatile)
696    {
697        Some(add_function_fields(
698            create_mutation_type(
699                generated,
700                mutation_root,
701                Arc::clone(&relationships),
702                Arc::clone(&type_names),
703                Arc::clone(&names),
704                max_rows,
705                role,
706            ),
707            generated,
708            true,
709            max_rows,
710            Arc::clone(&relationships),
711            Arc::clone(&names),
712        ))
713    } else {
714        None
715    };
716
717    // Create subscription type if enabled
718    let subscription = subscription_fields.map(|_| {
719        create_subscription_type(
720            generated,
721            subscription_root,
722            max_rows,
723            Arc::clone(&relationships),
724            Arc::clone(&names),
725            subscription_refresh,
726        )
727    });
728
729    // Build schema
730    let mut builder = Schema::build(
731        query_root,
732        mutation.as_ref().map(|_| mutation_root),
733        subscription.as_ref().map(|_| subscription_root),
734    );
735    if generated.enable_federation {
736        builder = builder
737            .enable_federation()
738            .extension(NullableFederationEntity);
739        let entity_state = Arc::new(EntityResolverState::new(
740            generated,
741            Arc::clone(&relationships),
742            Arc::clone(&names),
743            max_rows,
744        ));
745        builder = builder.entity_resolver(move |ctx| {
746            let state = Arc::clone(&entity_state);
747            FieldFuture::new(async move { resolve_entities(&ctx, state.as_ref()).await })
748        });
749    }
750
751    // Register all object types
752    for (_, obj) in object_types {
753        builder = builder.register(obj);
754    }
755
756    // Register query type
757    builder = builder.register(query);
758
759    // Register mutation type if present
760    if let Some(mutation) = mutation {
761        builder = builder.register(mutation);
762    }
763
764    // Register subscription type if present
765    if let Some(subscription) = subscription {
766        builder = builder.register(subscription);
767    }
768
769    // Register scalar types
770    // Every scalar the generated types actually name, rather than a fixed
771    // list. A `geometry` column, a `raster` column and a database enum each
772    // become a scalar under their own PostgreSQL name, because that is the
773    // name a client's query declares its variables with -- and a scalar the
774    // schema mentions but never registers makes the whole schema unbuildable.
775    let (bool_exp_inputs, bool_exp_scalars) = crate::input::bool_exp::build_inputs(
776        &generated.object_types,
777        &generated.relationship_fields,
778    );
779    for input in bool_exp_inputs {
780        builder = builder.register(input);
781    }
782
783    let mut scalar_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
784    for object in generated.object_types.values() {
785        // Both halves. A column a role may set without seeing is named by the
786        // insert input and by nothing a read produces, so collecting only the
787        // readable fields leaves its scalar unregistered -- and a scalar the
788        // schema mentions but never registers makes the whole schema
789        // unbuildable, which costs the role every field it has.
790        for field in object.fields.iter().chain(&object.writable_fields) {
791            scalar_names.insert(leaf_scalar_name(&field.graphql_type));
792        }
793    }
794    // Used as an argument type in its own right, whether or not any column is
795    // one: `objects`, `_set` and the mutation inputs are still JSON.
796    scalar_names.insert("JSON".to_string());
797    // A function's arguments name scalars of their own: a table may have no
798    // geometry column while a function takes one.
799    for function in &generated.function_fields {
800        for (_, pg_type, _) in &function.arguments {
801            scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
802        }
803    }
804    // The same for the functions behind computed fields, whose arguments are
805    // written where the field is asked for rather than at a root.
806    for object in generated.object_types.values() {
807        for field in &object.fields {
808            for (_, pg_type, _) in &field.arguments {
809                scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
810            }
811        }
812    }
813    for relationships in generated.relationship_fields.values() {
814        for relationship in relationships {
815            for (_, pg_type, _) in &relationship.arguments {
816                scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
817            }
818        }
819    }
820
821    // Every scalar the boolean expressions name, which is more than the
822    // scalars the columns are: a cast from a geometry names `geography`, and a
823    // raster comparison names `geometry`. Taken from what was generated rather
824    // than listed here, because a list here is a second place to keep in step
825    // and it fell out of step twice.
826    scalar_names.extend(bool_exp_scalars);
827
828    for name in scalar_names {
829        if matches!(name.as_str(), "Int" | "Float" | "String" | "Boolean" | "ID") {
830            continue;
831        }
832        // An enum table's type is an enum, not a scalar. It reaches this list
833        // because a column typed as one names it, and registering both would
834        // define the same type twice.
835        if generated.enum_types.contains_key(&name) {
836            continue;
837        }
838        builder = builder
839            .register(Scalar::new(&name).description(format!("The PostgreSQL `{}` type.", name)));
840    }
841
842    // Register the boolean expression inputs -- one per table, plus one
843    // comparison input per scalar the tables use.
844
845    // A key as one object, for `update_x_by_pk(pk_columns: {...})`. Only the
846    // by-key update spells its key this way; the by-key query and delete take
847    // the columns as arguments, and both spellings are what a generated client
848    // already sends.
849    let mut key_inputs: HashMap<String, InputObject> = HashMap::new();
850    for field in &generated.mutation_fields {
851        if field.mutation_type != MutationType::UpdateByPk || field.pk_columns.is_empty() {
852            continue;
853        }
854        let base_name = field.local_type_name.as_str();
855        let type_name = format!("{}_pk_columns_input", base_name);
856        let mut input = InputObject::new(&type_name)
857            .description(format!("The primary key of one {} row.", base_name));
858        for (column, pg_type) in &field.pk_columns {
859            input = input.field(InputValue::new(
860                names
861                    .column(&field.schema_name, &field.table_name, column)
862                    .unwrap_or(column),
863                TypeRef::named_nn(exposed_pk_argument_type(
864                    names.as_ref(),
865                    &field.schema_name,
866                    &field.table_name,
867                    column,
868                    pg_type,
869                )),
870            ));
871        }
872        key_inputs.insert(type_name, input);
873    }
874    for (_, input) in key_inputs {
875        builder = builder.register(input);
876    }
877
878    // What a mutation writes. `objects` and `_set` were `JSON`, which accepts
879    // anything and lets a client generate nothing: no completion, no codegen,
880    // and a misspelled column reaching the database instead of being caught
881    // before the request was sent. The same argument that made `where` a real
882    // type applies unchanged.
883    // Which types have an `_insert_input` at all, which is what a nested write
884    // names. Collected first because a relationship may point at a table this
885    // loop has not reached yet -- and because the answer is now a permission
886    // question: a role granted `insert` on `article` and only `select` on
887    // `author` gets an `article_insert_input`, and writing `author` inside it
888    // would name a type nobody registered. async-graphql refuses to build a
889    // schema with a dangling reference, so the whole role would have no schema
890    // over one field it was never allowed to use.
891    // Which types actually got each write input, recorded as they are built
892    // rather than predicted from the object type's fields. The two answers
893    // used to be the same and are not any more: an input is built from the
894    // write permission's columns, so a table whose readable columns include a
895    // number may still have no `_inc_input` -- and an argument naming a type
896    // nobody registered is a schema async-graphql refuses to build, which
897    // costs the role every field it has rather than the one it cannot use.
898    let insertable_types: HashSet<&str> = generated
899        .object_types
900        .iter()
901        .filter(|(_, object)| crate::input::mutation::is_insertable(&object.table))
902        .map(|(type_name, _)| type_name.as_str())
903        .collect();
904
905    for (type_name, object) in &generated.object_types {
906        let local_type_name = object.local_name.as_str();
907        let table = &object.table;
908        // Which columns a write may name, which is the write permission's
909        // answer and not the read permission's. A role granted `columns:
910        // ["age"]` on insert may not name `is_user` even where it can read it,
911        // and the corpus tests exactly that: `field 'is_user' not found in
912        // type: 'resident_insert_input'`.
913        // Only real columns are written. A computed field is a function of the
914        // row and a relationship is handled separately below.
915        let writable_named = |allowed: Option<&crate::names::ColumnSet>| {
916            object
917                .writable_fields
918                .iter()
919                .filter(|field| {
920                    let column = table_column_for(&names, table, &field.name);
921                    table.get_column(column).is_some()
922                        && allowed.is_none_or(|set| set.allows(column))
923                })
924                .collect::<Vec<&crate::schema::object::GraphQLField>>()
925        };
926        let writable = writable_named(None);
927        let insert_fields = write_fields(object, &names, role, crate::role::Verb::Insert);
928        let update_fields = write_fields(object, &names, role, crate::role::Verb::Update);
929        if writable.is_empty() {
930            continue;
931        }
932
933        let relationships = generated
934            .relationship_fields
935            .get(type_name)
936            .map(|r| r.as_slice())
937            .unwrap_or(&[]);
938        let conflict_type = match table.unique_constraints.is_empty() {
939            true => None,
940            false => Some(format!("{}_on_conflict", local_type_name)),
941        };
942
943        if crate::input::mutation::is_insertable(table) {
944            let mut insert = InputObject::new(format!("{}_insert_input", local_type_name))
945                .description(format!("The columns of a new {} row.", local_type_name));
946            let mut taken: HashSet<&str> = HashSet::new();
947            for field in &insert_fields {
948                // Every column optional: which ones the database insists on is
949                // the database's answer, and a column that is NOT NULL with a
950                // default does not have to be given.
951                taken.insert(field.name.as_str());
952                insert = insert.field(InputValue::new(
953                    &field.name,
954                    write_type_ref(&field.graphql_type),
955                ));
956            }
957            // A nested write: the rows to insert beside this one. Only where
958            // the far side is something this schema can write -- see
959            // `insertable_types`.
960            for relationship in relationships {
961                if !insertable_types.contains(relationship.target_type.as_str()) {
962                    continue;
963                }
964                if !taken.insert(relationship.name.as_str()) {
965                    continue;
966                }
967                let suffix = match relationship.is_list {
968                    true => "arr_rel_insert_input",
969                    false => "obj_rel_insert_input",
970                };
971                insert = insert.field(InputValue::new(
972                    &relationship.name,
973                    TypeRef::named(format!("{}_{}", relationship.target_local_name, suffix)),
974                ));
975            }
976            builder = builder.register(insert);
977
978            // How this table is written as somebody else's nested row. Both
979            // shapes carry an `on_conflict`, which is what makes a nested
980            // upsert expressible.
981            let data_type = format!("{}_insert_input", local_type_name);
982            let mut object_rel =
983                InputObject::new(format!("{}_obj_rel_insert_input", local_type_name))
984                    .description(format!(
985                        "One {} row written beside its parent.",
986                        local_type_name
987                    ))
988                    .field(InputValue::new("data", TypeRef::named_nn(&data_type)));
989            let mut array_rel =
990                InputObject::new(format!("{}_arr_rel_insert_input", local_type_name))
991                    .description(format!(
992                        "{} rows written beside their parent.",
993                        local_type_name
994                    ))
995                    .field(InputValue::new(
996                        "data",
997                        TypeRef::named_nn_list_nn(&data_type),
998                    ));
999            if let Some(conflict) = &conflict_type {
1000                object_rel =
1001                    object_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1002                array_rel =
1003                    array_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1004            }
1005            builder = builder.register(object_rel).register(array_rel);
1006        }
1007
1008        if crate::input::mutation::is_updatable(table) {
1009            let mut set = InputObject::new(format!("{}_set_input", local_type_name))
1010                .description(format!("Columns of {} to replace.", local_type_name));
1011            let mut numeric = InputObject::new(format!("{}_inc_input", local_type_name))
1012                .description(format!("Columns of {} to add to.", local_type_name));
1013            let mut any_numeric = false;
1014            let mut any_jsonb = false;
1015            for field in &update_fields {
1016                if matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1017                    any_jsonb = true;
1018                }
1019                set = set.field(InputValue::new(
1020                    &field.name,
1021                    write_type_ref(&field.graphql_type),
1022                ));
1023                // Only a number can be added to, and a list of them is not a
1024                // number: `_inc` takes the scalar or nothing.
1025                if crate::schema::aggregate::is_incrementable(&field.graphql_type) {
1026                    any_numeric = true;
1027                    numeric = numeric.field(InputValue::new(
1028                        &field.name,
1029                        TypeRef::named(leaf_scalar_name(&field.graphql_type)),
1030                    ));
1031                }
1032            }
1033            builder = builder.register(set);
1034            // A table with nothing to add to gets no type for adding to it.
1035            if any_numeric {
1036                builder = builder.register(numeric);
1037            }
1038
1039            // One entry of `update_x_many`: a filter and the values to write
1040            // where it matches. The whole list runs in one transaction, in the
1041            // order it was given, which is what makes it different from
1042            // sending the updates one at a time.
1043            let mut updates = InputObject::new(format!("{}_updates", local_type_name))
1044                .description(format!(
1045                    "One update to {}: which rows, and what to write.",
1046                    local_type_name
1047                ))
1048                .field(InputValue::new(
1049                    "where",
1050                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1051                ))
1052                .field(InputValue::new(
1053                    "_set",
1054                    TypeRef::named(format!("{}_set_input", local_type_name)),
1055                ));
1056            if any_numeric {
1057                updates = updates.field(InputValue::new(
1058                    "_inc",
1059                    TypeRef::named(format!("{}_inc_input", local_type_name)),
1060                ));
1061            }
1062            // What a document column may be told to do, one input per
1063            // operator: each takes a value of a different shape -- a
1064            // document, a key, an index, a path -- and only the columns that
1065            // hold documents may be told any of it. A table with none is not
1066            // given the operators at all, which is what says in the schema
1067            // that `_append` is not a thing to write there.
1068            for (operator, _) in JSONB_OPERATORS {
1069                if !any_jsonb {
1070                    break;
1071                }
1072                updates = updates.field(InputValue::new(
1073                    *operator,
1074                    TypeRef::named(jsonb_operator_input(local_type_name, operator)),
1075                ));
1076            }
1077            builder = builder.register(updates);
1078            if any_jsonb {
1079                for (operator, item) in JSONB_OPERATORS {
1080                    let mut input =
1081                        InputObject::new(jsonb_operator_input(local_type_name, operator))
1082                            .description(format!(
1083                                "Columns of {} to apply `{}` to.",
1084                                local_type_name, operator
1085                            ));
1086                    for field in &update_fields {
1087                        if !matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1088                            continue;
1089                        }
1090                        input = input.field(InputValue::new(&field.name, item()));
1091                    }
1092                    builder = builder.register(input);
1093                }
1094            }
1095        }
1096    }
1097
1098    // A function's own arguments, under a name of their own so they cannot
1099    // collide with `where` or `limit`.
1100    for function in &generated.function_fields {
1101        if function.arguments.is_empty() {
1102            continue;
1103        }
1104        let mut args = InputObject::new(format!("{}_args", function.name))
1105            .description(format!("Arguments to {}.", function.name));
1106        for (name, pg_type, _) in &function.arguments {
1107            let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1108            // Nullable, whether or not the parameter has a default. Every
1109            // PostgreSQL argument accepts a null, and a client passing one
1110            // through a variable declares it as the nullable type -- `query
1111            // ($point: json)` used where `json!` was expected is a query the
1112            // spec's variable rule refuses. Whether a defaulted argument may
1113            // be left out entirely is enforced where the call is written,
1114            // which is the only place that can tell.
1115            args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1116        }
1117        builder = builder.register(args);
1118    }
1119
1120    // The same for a computed relationship's function, which takes its
1121    // arguments where it is embedded rather than at the root. Named after the
1122    // type and the field, since two tables may reach the same function and one
1123    // table may reach two.
1124    for (type_name, relationships) in &generated.relationship_fields {
1125        let local_type_name = generated
1126            .object_types
1127            .get(type_name)
1128            .map(|object| object.local_name.as_str())
1129            .unwrap_or(type_name);
1130        for relationship in relationships {
1131            if relationship.arguments.is_empty() {
1132                continue;
1133            }
1134            let mut args =
1135                InputObject::new(computed_args_type_name(local_type_name, &relationship.name))
1136                    .description(format!(
1137                        "Arguments to {}, beside the row it is asked of.",
1138                        relationship.name
1139                    ));
1140            for (name, pg_type, _) in &relationship.arguments {
1141                let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1142                // Nullable, for the reason the function arguments above are.
1143                args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1144            }
1145            builder = builder.register(args);
1146        }
1147    }
1148
1149    // And for a computed *column*'s function, which likewise takes its
1150    // arguments where the field is asked for. `locations { distance(args: {
1151    // from: ... }) }` -- the field is a function of the row and of what the
1152    // caller wants measured against it.
1153    for object in generated.object_types.values() {
1154        let local_type_name = object.local_name.as_str();
1155        for field in &object.fields {
1156            if field.arguments.is_empty() {
1157                continue;
1158            }
1159            let mut args = InputObject::new(computed_args_type_name(local_type_name, &field.name))
1160                .description(format!(
1161                    "Arguments to {}, beside the row it is asked of.",
1162                    field.name
1163                ));
1164            for (name, pg_type, _) in &field.arguments {
1165                let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1166                // Nullable, for the reason the function arguments above are.
1167                args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1168            }
1169            builder = builder.register(args);
1170        }
1171    }
1172
1173    // What a predicate over a related set may ask. Registered only for the
1174    // tables something points at with a to-many relationship, which is the
1175    // only place the field naming them exists.
1176    {
1177        use crate::input::bool_exp as be;
1178        let mut targets: BTreeMap<&str, &str> = BTreeMap::new();
1179        for relationships in generated.relationship_fields.values() {
1180            for relationship in relationships {
1181                if relationship.is_list {
1182                    targets.insert(
1183                        relationship.target_local_name.as_str(),
1184                        relationship.target_type.as_str(),
1185                    );
1186                }
1187            }
1188        }
1189        for (target, target_type) in targets {
1190            let Some(object) = generated.object_types.get(target_type) else {
1191                continue;
1192            };
1193            // A boolean column is what `bool_and` and `bool_or` fold, and a
1194            // GraphQL enum may not be empty -- so a table with none gets
1195            // neither the aggregates nor the enum naming their columns.
1196            let booleans: Vec<&str> = object
1197                .fields
1198                .iter()
1199                .filter(|field| {
1200                    object.table.get_column(&field.name).is_some()
1201                        && matches!(field.graphql_type, crate::types::GraphQLType::Boolean)
1202                })
1203                .map(|field| field.name.as_str())
1204                .collect();
1205
1206            let mut over = InputObject::new(be::aggregate_bool_exp_type_name(target)).description(
1207                format!("A question about the whole set of related {} rows.", target),
1208            );
1209            for function in be::AGGREGATE_PREDICATES {
1210                if function != "count" && booleans.is_empty() {
1211                    continue;
1212                }
1213                over = over.field(InputValue::new(
1214                    function,
1215                    TypeRef::named(be::aggregate_bool_exp_function(target, function)),
1216                ));
1217
1218                // `count` may be told which columns to count, as the field
1219                // itself may; `bool_and` folds exactly one.
1220                let (arguments, predicate) = match function {
1221                    "count" => (
1222                        TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1223                            target,
1224                        )),
1225                        be::comparison_type_name("Int"),
1226                    ),
1227                    _ => (
1228                        TypeRef::named_nn(be::aggregate_bool_exp_columns(target, function)),
1229                        be::comparison_type_name("Boolean"),
1230                    ),
1231                };
1232                let mut input = InputObject::new(be::aggregate_bool_exp_function(target, function))
1233                    .description(format!(
1234                        "`{}` over the related {} rows, and what it has to be.",
1235                        function, target
1236                    ))
1237                    .field(InputValue::new("arguments", arguments))
1238                    .field(InputValue::new("distinct", TypeRef::named("Boolean")))
1239                    .field(InputValue::new(
1240                        "filter",
1241                        TypeRef::named(be::bool_exp_type_name(target)),
1242                    ))
1243                    .field(InputValue::new("predicate", TypeRef::named_nn(predicate)));
1244                if function == "count" {
1245                    input = input.description(format!(
1246                        "How many related {} rows there have to be.",
1247                        target
1248                    ));
1249                }
1250                builder = builder.register(input);
1251
1252                if function != "count" {
1253                    let mut columns = Enum::new(be::aggregate_bool_exp_columns(target, function))
1254                        .description(format!("A boolean column of {}.", target));
1255                    for name in &booleans {
1256                        columns = columns.item(EnumItem::new(*name));
1257                    }
1258                    builder = builder.register(columns);
1259                }
1260            }
1261            builder = builder.register(over);
1262        }
1263    }
1264
1265    // Upserts. A table with no unique constraint has no conflict to resolve,
1266    // and a GraphQL enum may not be empty, so it gets none of these types
1267    // rather than an unusable set of them.
1268    for object in generated.object_types.values() {
1269        let local_type_name = object.local_name.as_str();
1270        // What an upsert may write, which is the update permission's answer
1271        // and not the read permission's: `update_columns` names columns to
1272        // overwrite, so a role that may set a column without seeing it may
1273        // name it here. Where no update permission narrows them these are the
1274        // table's own columns, which is what the enum listed before.
1275        let updatable_columns = write_fields(object, &names, role, crate::role::Verb::Update);
1276        if object.table.unique_constraints.is_empty() {
1277            continue;
1278        }
1279
1280        let mut constraints =
1281            Enum::new(format!("{}_constraint", local_type_name)).description(format!(
1282                "A uniqueness of {} that an insert may conflict with.",
1283                local_type_name
1284            ));
1285        for (name, columns) in &object.table.unique_constraints {
1286            constraints = constraints
1287                .item(EnumItem::new(name).description(format!("unique ({})", columns.join(", "))));
1288        }
1289
1290        let mut updatable = Enum::new(format!("{}_update_column", local_type_name)).description(
1291            format!("A column of {} an upsert may write.", local_type_name),
1292        );
1293        for field in &updatable_columns {
1294            updatable = updatable.item(EnumItem::new(&field.name));
1295        }
1296        // An update permission granting no column leaves the enum with no
1297        // members, which is not a legal type -- and dropping the enum would
1298        // drop `on_conflict` with it, so an insert that names one would fail
1299        // to build rather than be refused. Hasura's answer is a member that
1300        // names no column: the upsert is still expressible, `update_columns:
1301        // []` still means `DO NOTHING`, and naming the placeholder is refused
1302        // where it is used. `article_on_conflict_restricted_role.yaml` tests
1303        // both halves.
1304        if updatable_columns.is_empty() {
1305            updatable = updatable.item(
1306                EnumItem::new(PLACEHOLDER_COLUMN)
1307                    .description("No column may be written by this upsert."),
1308            );
1309        }
1310
1311        let on_conflict = InputObject::new(format!("{}_on_conflict", local_type_name))
1312            .description(format!(
1313                "What to do when an insert into {} conflicts.",
1314                local_type_name
1315            ))
1316            .field(InputValue::new(
1317                "constraint",
1318                TypeRef::named_nn(format!("{}_constraint", local_type_name)),
1319            ))
1320            // An empty list is `DO NOTHING`, which is how Hasura spells "leave
1321            // the row that is already there alone" -- and is the default, so
1322            // `on_conflict: {constraint: article_pkey}` means exactly that
1323            // rather than being refused for saying nothing about columns.
1324            .field(
1325                InputValue::new(
1326                    "update_columns",
1327                    TypeRef::named_nn_list_nn(format!("{}_update_column", local_type_name)),
1328                )
1329                .default_value(Value::List(Vec::new())),
1330            )
1331            .field(InputValue::new(
1332                "where",
1333                TypeRef::named(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1334            ));
1335
1336        builder = builder
1337            .register(constraints)
1338            .register(updatable)
1339            .register(on_conflict);
1340    }
1341
1342    // The enums generated from enum tables.
1343    for (type_name, members) in &generated.enum_types {
1344        let mut generated_enum =
1345            Enum::new(type_name).description(format!("The values {} allows.", type_name));
1346        for (value, comment) in members {
1347            let item = EnumItem::new(value);
1348            generated_enum = generated_enum.item(match comment {
1349                Some(description) => item.description(description),
1350                None => item,
1351            });
1352        }
1353        builder = builder.register(generated_enum);
1354    }
1355
1356    // Ordering: one input per table, one column enum per table, and the single
1357    // direction enum they all share.
1358    let (order_inputs, order_enums) = crate::input::order_by::build_inputs(
1359        &generated.object_types,
1360        &generated.relationship_fields,
1361    );
1362    for input in order_inputs {
1363        builder = builder.register(input);
1364    }
1365    for enum_type in order_enums {
1366        builder = builder.register(enum_type);
1367    }
1368
1369    builder
1370        .finish()
1371        .map_err(|e| GraphQLError::SchemaError(e.to_string()))
1372}
1373
1374/// Create an object type from a TableObjectType.
1375/// The enum member that stands in for an update permission granting no column.
1376///
1377/// Hasura's spelling, and it is load-bearing: `article_update_column` must be
1378/// a legal enum for `article_on_conflict` to exist, and `on_conflict` must
1379/// exist for an insert that carries one to be refused rather than unbuildable.
1380const PLACEHOLDER_COLUMN: &str = "_PLACEHOLDER";
1381
1382/// The name of a table's mutation response type.
1383pub fn mutation_response_type_name(base_name: &str) -> String {
1384    format!("{}_mutation_response", base_name)
1385}
1386
1387/// Build the `<table>_mutation_response` object.
1388///
1389/// Every bulk mutation answers with this rather than with the rows directly.
1390/// `affected_rows` is the count PostgreSQL reports, which is not the length of
1391/// `returning`: a client may ask for no rows back at all and still need to know
1392/// how many were touched, and that is the usual case for a delete.
1393fn create_mutation_response_type(base_name: &str, row_type: &str, returning: bool) -> Object {
1394    let response_name = mutation_response_type_name(base_name);
1395
1396    let response = Object::new(&response_name)
1397        .description(format!("The rows {} changed, and how many.", base_name))
1398        .field(Field::new(
1399            "affected_rows",
1400            TypeRef::named_nn(TypeRef::INT),
1401            |ctx| {
1402                FieldFuture::new(async move {
1403                    if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1404                        if let Some(value) = map.get(&async_graphql::Name::new("affected_rows")) {
1405                            return Ok(Some(FieldValue::value(value.clone())));
1406                        }
1407                    }
1408                    Ok(Some(FieldValue::value(Value::from(0))))
1409                })
1410            },
1411        ));
1412    // A table this role may write and not read has no row type, so there is
1413    // nothing for `returning` to be a list of. Hasura answers such a write
1414    // with the count alone, and the corpus asks for nothing else.
1415    if !returning {
1416        return response;
1417    }
1418    response.field(Field::new(
1419        "returning",
1420        TypeRef::named_nn_list_nn(row_type.to_string()),
1421        |ctx| {
1422            FieldFuture::new(async move {
1423                let rows = match ctx.parent_value.as_value() {
1424                    Some(Value::Object(map)) => {
1425                        match map.get(&async_graphql::Name::new("returning")) {
1426                            Some(Value::List(items)) => items.clone(),
1427                            _ => Vec::new(),
1428                        }
1429                    }
1430                    _ => Vec::new(),
1431                };
1432                Ok(Some(FieldValue::list(
1433                    rows.into_iter().map(FieldValue::value),
1434                )))
1435            })
1436        },
1437    ))
1438}
1439
1440/// Build every aggregate type for one table.
1441///
1442/// Four kinds, and they nest: `<t>_aggregate` holds `aggregate` and `nodes`,
1443/// `<t>_aggregate_fields` holds `count` and one field per function, and each
1444/// function has a type of its own holding the columns it applies to.
1445fn create_aggregate_types(base_name: &str, object: &TableObjectType) -> Vec<Object> {
1446    use crate::schema::aggregate as agg;
1447
1448    let mut types = Vec::new();
1449
1450    // `<t>_aggregate`: the rows, and the numbers about them.
1451    let fields_type = agg::aggregate_fields_type_name(base_name);
1452    // Whether there are rows to hand back beside the numbers. A role granted
1453    // "how many" and not "which" has no row type, so `nodes` has nothing to be
1454    // a list of.
1455    let has_rows = !object.fields.is_empty();
1456    let mut over = Object::new(agg::aggregate_type_name(base_name))
1457        .description(match has_rows {
1458            true => format!("Aggregates over {}, with the rows themselves.", base_name),
1459            false => format!("Aggregates over {}.", base_name),
1460        })
1461        .field(Field::new(
1462            "aggregate",
1463            TypeRef::named(&fields_type),
1464            |ctx| FieldFuture::new(async move { Ok(child_of(&ctx, "aggregate")) }),
1465        ));
1466    if has_rows {
1467        over = over.field(Field::new(
1468            "nodes",
1469            TypeRef::named_nn_list_nn(object.name.clone()),
1470            |ctx| {
1471                FieldFuture::new(async move {
1472                    let rows = match child_value(&ctx, "nodes") {
1473                        Some(Value::List(items)) => items,
1474                        _ => Vec::new(),
1475                    };
1476                    Ok(Some(FieldValue::list(
1477                        rows.into_iter().map(FieldValue::value),
1478                    )))
1479                })
1480            },
1481        ));
1482    }
1483    types.push(over);
1484
1485    // `<t>_aggregate_fields`: count, and one field per function.
1486    let mut count = Field::new("count", TypeRef::named_nn(TypeRef::INT), |ctx| {
1487        // Read under the name it was asked for, not under `count`.
1488        // Two counts of different things sit in one selection --
1489        // `count` beside `distinct_authors: count(columns: [author_id],
1490        // distinct: true)` -- and they are different numbers.
1491        let key = ctx.ctx.field().alias().unwrap_or("count").to_string();
1492        FieldFuture::new(async move {
1493            Ok(Some(FieldValue::value(
1494                child_value(&ctx, &key).unwrap_or(Value::from(0)),
1495            )))
1496        })
1497    });
1498    // `count(columns:)` counts the rows where those columns are not null, and
1499    // `distinct` counts distinct values among them. Neither is offered where
1500    // the role may name no column: `count` is then a count of rows and nothing
1501    // else, and the corpus tests exactly that -- `'count' has no argument
1502    // named 'columns'`.
1503    if has_rows {
1504        count = count
1505            .argument(InputValue::new(
1506                "columns",
1507                TypeRef::named_nn_list(agg_select_column(base_name)),
1508            ))
1509            .argument(InputValue::new("distinct", TypeRef::named("Boolean")));
1510    }
1511    let mut aggregate_fields = Object::new(&fields_type)
1512        .description(format!("Aggregate functions over {}.", base_name))
1513        .field(count);
1514
1515    for (function, returns, columns) in agg::functions_for(object) {
1516        let function_type = agg::function_fields_type_name(base_name, function);
1517        let mut per_column = Object::new(&function_type).description(format!(
1518            "`{}` of each {} column it applies to.",
1519            function, base_name
1520        ));
1521        for column in &columns {
1522            let column_name = column.clone();
1523            let type_name = agg::field_type_for(object, column, returns);
1524            per_column =
1525                per_column.field(Field::new(column, TypeRef::named(type_name), move |ctx| {
1526                    let column_name = column_name.clone();
1527                    FieldFuture::new(async move {
1528                        Ok(child_value(&ctx, &column_name).map(FieldValue::value))
1529                    })
1530                }));
1531        }
1532        types.push(per_column);
1533
1534        let owned = function.to_string();
1535        aggregate_fields = aggregate_fields.field(Field::new(
1536            function,
1537            TypeRef::named(&function_type),
1538            move |ctx| {
1539                let owned = owned.clone();
1540                FieldFuture::new(async move { Ok(child_of(&ctx, &owned)) })
1541            },
1542        ));
1543    }
1544
1545    types.push(aggregate_fields);
1546    types
1547}
1548
1549/// The column enum a `count(columns:)` draws from.
1550fn agg_select_column(base_name: &str) -> String {
1551    crate::input::order_by::select_column_type_name(base_name)
1552}
1553
1554/// One key of the parent object, as a value.
1555fn child_value(ctx: &ResolverContext<'_>, key: &str) -> Option<Value> {
1556    match ctx.parent_value.as_value() {
1557        Some(Value::Object(map)) => map.get(&async_graphql::Name::new(key)).cloned(),
1558        _ => None,
1559    }
1560}
1561
1562/// One key of the parent object, as a resolvable child.
1563///
1564/// `None` where the key is absent or null, which is what a client that did not
1565/// ask for that aggregate should see.
1566fn child_of<'a>(ctx: &ResolverContext<'_>, key: &str) -> Option<FieldValue<'a>> {
1567    match child_value(ctx, key) {
1568        None | Some(Value::Null) => None,
1569        Some(value) => Some(FieldValue::value(value)),
1570    }
1571}
1572
1573/// An error carrying the code Hasura would give it.
1574///
1575/// The default coding reads the message, and a refusal written here has no
1576/// word in it that a message from PostgreSQL would not also have -- so it says
1577/// so itself rather than being guessed at.
1578fn coded_error(code: &'static str, message: impl Into<String>) -> async_graphql::Error {
1579    let mut error = async_graphql::Error::new(message);
1580    let mut extensions = async_graphql::ErrorExtensionValues::default();
1581    extensions.set("code", code);
1582    error.extensions = Some(extensions);
1583    error
1584}
1585
1586/// A refusal the client could have avoided by writing the request differently.
1587fn validation_error(message: impl Into<String>) -> async_graphql::Error {
1588    coded_error("validation-failed", message)
1589}
1590
1591/// A database error, classified the way Hasura classifies it.
1592///
1593/// PostgreSQL's own message is the whole of what either server says about
1594/// what went wrong; what Hasura adds is a word for *which kind* of wrong,
1595/// taken from the SQLSTATE rather than from the text: `Uniqueness violation.
1596/// duplicate key value violates unique constraint "author_name_key"`. A client
1597/// reporting that to a user is reporting text it already ships.
1598///
1599/// Read from the code, never from the message. The message is localised, the
1600/// code is not, and every attempt to tell these apart by reading English is
1601/// wrong the first time a server runs in another language.
1602fn database_error(error: sqlx::Error) -> async_graphql::Error {
1603    let sqlx::Error::Database(db) = &error else {
1604        return async_graphql::Error::new(error.to_string());
1605    };
1606    let described = match db.code().as_deref() {
1607        // Class 23, integrity constraint violation: the four PostgreSQL
1608        // raises and Hasura names.
1609        Some("23505") => "Uniqueness violation. ",
1610        Some("23502") => "Not-NULL violation. ",
1611        Some("23503") => "Foreign key violation. ",
1612        Some("23514") => "Check constraint violation. ",
1613        // Everything else is answered with PostgreSQL's own words and no
1614        // heading. Inventing one for a class Hasura says nothing about would
1615        // be a difference rather than a match.
1616        _ => "",
1617    };
1618    let error = async_graphql::Error::new(format!("{}{}", described, db.message()));
1619    // And the code, where the SQLSTATE says which one. Only the codes Hasura's
1620    // corpus pins are set: everything else is left for `code_for` to classify,
1621    // which is a guess from the message text and says so. Replacing that guess
1622    // wholesale would be the same guess with fewer places to notice it.
1623    let coded = match db.code().as_deref() {
1624        Some(code) if code.starts_with("23") => Some("constraint-violation"),
1625        // A `LIKE` pattern that ends mid-escape. Not a data exception to
1626        // Hasura -- the pattern came from the request, so the request is what
1627        // was wrong.
1628        Some("22025") => Some("bad-request"),
1629        // A negative `OFFSET`, and the rest of class 22 with it.
1630        Some(code) if code.starts_with("22") => Some("data-exception"),
1631        _ => None,
1632    };
1633    match coded {
1634        Some(code) => at_code(error, code),
1635        None => error,
1636    }
1637}
1638
1639/// The same error, coded as Hasura codes it.
1640fn at_code(mut error: async_graphql::Error, code: &'static str) -> async_graphql::Error {
1641    let mut extensions = error.extensions.take().unwrap_or_default();
1642    extensions.set("code", code);
1643    error.extensions = Some(extensions);
1644    error
1645}
1646
1647/// The same error, told where in the request it happened.
1648///
1649/// Hasura names the place a write went wrong inside the *arguments* --
1650/// `$.selectionSet.insert_author.args.objects[0].bio` -- and a response path
1651/// cannot say that: it names fields of the answer, and the answer has no
1652/// `objects`. So the path is written where the error is raised, by whoever
1653/// knows which row and which column it is about, and
1654/// [`crate::hasura::path_for`] prefers it over the response path.
1655fn at_path(mut error: async_graphql::Error, path: &str) -> async_graphql::Error {
1656    let mut extensions = error.extensions.take().unwrap_or_default();
1657    extensions.set("path", path);
1658    error.extensions = Some(extensions);
1659    error
1660}
1661
1662fn create_object_type(
1663    obj: &TableObjectType,
1664    relationships: &[RelationshipField],
1665    federation_entity: Option<&FederationEntity>,
1666) -> Object {
1667    let mut object = Object::new(&obj.name);
1668    let entity_is_shared = federation_entity.is_some_and(|entity| entity.shared);
1669    if let Some(entity) = federation_entity {
1670        object = object.key(entity.key_fields_sdl());
1671    }
1672
1673    // A table with no comment still gets a description, because Hasura gives
1674    // one and a client generating documentation from the schema would
1675    // otherwise show a blank where it used to show this.
1676    object = match obj.description() {
1677        // An empty description is one that was given and is empty: metadata
1678        // said the type has none, which is not the same as saying nothing.
1679        Some("") => object,
1680        Some(desc) => object.description(desc),
1681        None => object.description(format!(
1682            "columns and relationships of \"{}\"",
1683            obj.table_name()
1684        )),
1685    };
1686
1687    // A GraphQL object may not have two fields of one name, and a schema that
1688    // tries to build one aborts the process rather than returning an error.
1689    // Relationship names are derived from the table they point at, so a
1690    // foreign key column named after its own target -- `pizza.crust`
1691    // referencing `crust`, which is an ordinary way to write that schema --
1692    // produces exactly that clash.
1693    //
1694    // The column wins. It is the table's own data, it is what a client's
1695    // existing queries select, and dropping it to keep a derived name would
1696    // lose something the schema actually says. The relationship is left out
1697    // and said so, which is what Hasura does with the same clash: the
1698    // metadata is marked inconsistent and the field is simply not there.
1699    let mut taken: HashSet<String> = obj.fields.iter().map(|f| f.name.clone()).collect();
1700
1701    // Collected rather than added as they are built, so they can go on in name
1702    // order -- which is the order Hasura answers introspection in, and does not
1703    // depend on which column the catalogue happened to list first.
1704    let mut fields: Vec<(String, Field)> = Vec::new();
1705
1706    for field in &obj.fields {
1707        let field_name = field.name.clone();
1708        let field_type = graphql_type_ref(&field.type_string());
1709        // A document-valued column can be asked for one part of itself, the
1710        // same way `#>` reads one. It is answered here rather than in SQL
1711        // because the same column may be asked for under several aliases --
1712        // `c32_json(path: "a")` beside `c32_json(path: "arr[0]")` -- and
1713        // one projection cannot carry both.
1714        let takes_path = matches!(&field.graphql_type, crate::types::GraphQLType::Json)
1715            || matches!(
1716                &field.graphql_type,
1717                crate::types::GraphQLType::Custom(name) if name == "json"
1718            );
1719
1720        // Create field with resolver that extracts from parent async_graphql::Value
1721        // The query resolver stores rows as FieldValue::value(Value::Object)
1722        // so we use as_value() to get the Value and extract fields from the Object
1723        let gql_field = Field::new(&field.name, field_type, move |ctx| {
1724            let field_name = field_name.clone();
1725            FieldFuture::new(async move {
1726                // Get the parent value as async_graphql::Value using as_value()
1727                if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1728                    // Convert field name to async_graphql::Name for lookup
1729                    let key = async_graphql::Name::new(&field_name);
1730                    let path = match takes_path {
1731                        true => ctx.args.get("path").and_then(|v| v.string().ok()),
1732                        false => None,
1733                    };
1734                    match map.get(&key) {
1735                        // A null is the answer, not a value to resolve. An
1736                        // enum-typed column said so with `internal: invalid
1737                        // item for enum` rather than answering null, because a
1738                        // null is not one of its members.
1739                        Some(Value::Null) => return Ok(None),
1740                        Some(val) => {
1741                            let val = match path {
1742                                Some(path) => walk_json_path(val, path)?,
1743                                None => val.clone(),
1744                            };
1745                            return match val {
1746                                Value::Null => Ok(None),
1747                                val => Ok(Some(FieldValue::value(val))),
1748                            };
1749                        }
1750                        None => {}
1751                    }
1752                }
1753
1754                // Field not found or parent not a Value::Object
1755                Ok(None)
1756            })
1757        });
1758
1759        let gql_field = match takes_path {
1760            true => gql_field.argument(
1761                InputValue::new("path", TypeRef::named("String")).description("JSON select path"),
1762            ),
1763            false => gql_field,
1764        };
1765
1766        // A computed field whose function takes more than the row: the
1767        // caller writes those under `args`, for the reason a computed
1768        // relationship's are written there.
1769        let gql_field = match field.arguments.is_empty() {
1770            true => gql_field,
1771            false => gql_field.argument(InputValue::new(
1772                "args",
1773                TypeRef::named_nn(computed_args_type_name(&obj.local_name, &field.name)),
1774            )),
1775        };
1776
1777        let gql_field = if let Some(desc) = &field.description {
1778            gql_field.description(desc)
1779        } else {
1780            gql_field
1781        };
1782        let gql_field = if entity_is_shared && !field.is_pk {
1783            gql_field.shareable()
1784        } else {
1785            gql_field
1786        };
1787
1788        fields.push((field.name.clone(), gql_field));
1789    }
1790
1791    // Relationship fields. The query resolver embeds related rows into the
1792    // parent JSON before returning it, so these read from the parent value the
1793    // same way column fields do.
1794    for rel in relationships {
1795        if !taken.insert(rel.name.clone()) {
1796            tracing::warn!(
1797                "{}: not exposing the relationship to \"{}\" as `{}` -- the table \
1798                 already has a column of that name",
1799                obj.name,
1800                rel.target_type,
1801                rel.name
1802            );
1803            continue;
1804        }
1805        let field_name = rel.name.clone();
1806        let field_type = if rel.is_list {
1807            TypeRef::named_nn_list_nn(&rel.target_type)
1808        } else if always_present(rel, &obj.table) {
1809            TypeRef::named_nn(&rel.target_type)
1810        } else {
1811            TypeRef::named(&rel.target_type)
1812        };
1813
1814        let is_list = rel.is_list;
1815        let mut gql_field = Field::new(&rel.name, field_type, move |ctx| {
1816            let field_name = field_name.clone();
1817            FieldFuture::new(async move {
1818                if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1819                    let key = async_graphql::Name::new(&field_name);
1820                    match map.get(&key) {
1821                        // A row that points at nothing has no related row, and
1822                        // the answer to that is null -- not an object whose
1823                        // every field is null, which is what handing a null
1824                        // parent value to the field resolvers produced.
1825                        Some(Value::Null) if !is_list => return Ok(None),
1826                        Some(val) => return Ok(Some(FieldValue::value(val.clone()))),
1827                        None => {}
1828                    }
1829                }
1830                Ok(None)
1831            })
1832        });
1833
1834        // The same arguments the root field takes. An embedded list is a list
1835        // like any other: a client showing the five most recent articles of an
1836        // author has nowhere else to say so, and without these the only way to
1837        // narrow one is to fetch all of it and discard the rest in the client.
1838        // A to-one relationship is left alone -- there is nothing to order or
1839        // page through when the answer is one row.
1840        // A computed relationship's function may take more than the row --
1841        // "the articles of this author matching a search" -- and `args` is
1842        // where the caller writes them, so a term called `limit` cannot shadow
1843        // the one that pages the result.
1844        if !rel.arguments.is_empty() {
1845            gql_field = gql_field.argument(InputValue::new(
1846                "args",
1847                TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1848            ));
1849        }
1850        if rel.is_list {
1851            gql_field = gql_field
1852                .argument(InputValue::new(
1853                    "distinct_on",
1854                    TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1855                        &rel.target_local_name,
1856                    )),
1857                ))
1858                .argument(InputValue::new(
1859                    "where",
1860                    TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1861                        &rel.target_local_name,
1862                    )),
1863                ))
1864                .argument(InputValue::new(
1865                    "order_by",
1866                    TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1867                        &rel.target_local_name,
1868                    )),
1869                ))
1870                .argument(InputValue::new("limit", TypeRef::named("Int")))
1871                .argument(InputValue::new("offset", TypeRef::named("Int")));
1872        }
1873
1874        let gql_field = if let Some(desc) = &rel.description {
1875            gql_field.description(desc)
1876        } else {
1877            gql_field
1878        };
1879        let gql_field = if entity_is_shared {
1880            gql_field.shareable()
1881        } else {
1882            gql_field
1883        };
1884
1885        fields.push((rel.name.clone(), gql_field));
1886
1887        // `author { articles_aggregate { aggregate { count } } }` -- the count
1888        // of a row's children without fetching them, which is the query behind
1889        // every "12 comments" beside a post. Only for a relationship to many:
1890        // counting one row is not a question anyone asks.
1891        if rel.is_list {
1892            let aggregate_field = format!("{}_aggregate", rel.name);
1893            if taken.insert(aggregate_field.clone()) {
1894                let key = aggregate_field.clone();
1895                fields.push((
1896                    aggregate_field.clone(),
1897                    Field::new(
1898                        &aggregate_field,
1899                        TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
1900                            &rel.target_local_name,
1901                        )),
1902                        move |ctx| {
1903                            let key = key.clone();
1904                            FieldFuture::new(async move {
1905                                // Absent where the parent has no children at
1906                                // all; the aggregate type's own resolvers read
1907                                // a missing key as zero and an empty list.
1908                                Ok(Some(FieldValue::value(
1909                                    child_value(&ctx, &key).unwrap_or(Value::Null),
1910                                )))
1911                            })
1912                        },
1913                    )
1914                    // The same arguments the list itself takes. "How many of
1915                    // this author's articles were published this year" is the
1916                    // question a count is usually asked as, and without a
1917                    // `where` there is no way to write it.
1918                    .argument(InputValue::new(
1919                        "distinct_on",
1920                        TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1921                            &rel.target_local_name,
1922                        )),
1923                    ))
1924                    .argument(InputValue::new(
1925                        "where",
1926                        TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1927                            &rel.target_local_name,
1928                        )),
1929                    ))
1930                    .argument(InputValue::new(
1931                        "order_by",
1932                        TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1933                            &rel.target_local_name,
1934                        )),
1935                    ))
1936                    .argument(InputValue::new("limit", TypeRef::named("Int")))
1937                    .argument(InputValue::new("offset", TypeRef::named("Int")))
1938                    .description(format!("Aggregates over {}.", rel.name)),
1939                ));
1940                // Counting the rows a function answers with means calling it,
1941                // so the aggregate takes whatever the function takes.
1942                if !rel.arguments.is_empty() {
1943                    let last = fields.len() - 1;
1944                    let (name, field) = fields.remove(last);
1945                    fields.push((
1946                        name,
1947                        field.argument(InputValue::new(
1948                            "args",
1949                            TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1950                        )),
1951                    ));
1952                }
1953                if entity_is_shared {
1954                    let last = fields.len() - 1;
1955                    let (name, field) = fields.remove(last);
1956                    fields.push((name, field.shareable()));
1957                }
1958            }
1959        }
1960    }
1961
1962    fields.sort_by(|(a, _), (b, _)| a.cmp(b));
1963    for (_, field) in fields {
1964        object = object.field(field);
1965    }
1966
1967    object
1968}
1969
1970/// A root field, whichever root it is on.
1971///
1972/// A query field and a subscription field take the same arguments and mean the
1973/// same by them -- the difference is only whether the answer is sent once or
1974/// whenever it changes -- and the two builders share no trait of their own, so
1975/// this is what lets the argument list be written once.
1976trait RootField: Sized {
1977    fn with_argument(self, value: InputValue) -> Self;
1978}
1979
1980impl RootField for Field {
1981    fn with_argument(self, value: InputValue) -> Self {
1982        self.argument(value)
1983    }
1984}
1985
1986impl RootField for SubscriptionField {
1987    fn with_argument(self, value: InputValue) -> Self {
1988        self.argument(value)
1989    }
1990}
1991
1992/// The five arguments that narrow a set of rows, in the order Hasura lists
1993/// them.
1994fn with_row_arguments<F: RootField>(field: F, type_name: &str) -> F {
1995    with_row_arguments_named(field, type_name, true)
1996}
1997
1998/// The same, where `columns` says whether the table has any to name.
1999///
2000/// A table a role may count and not read has none: there is no column enum to
2001/// order by and none to be distinct on, so those two arguments are not there
2002/// -- and could not be, since the types they name are not built either. What
2003/// is left still narrows the set being counted, which is the whole of what
2004/// such a root is for.
2005fn with_row_arguments_named<F: RootField>(field: F, type_name: &str, columns: bool) -> F {
2006    let mut field = field;
2007    if columns {
2008        field = field.with_argument(InputValue::new(
2009            "distinct_on",
2010            TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(type_name)),
2011        ));
2012    }
2013    field = field
2014        .with_argument(InputValue::new("limit", TypeRef::named("Int")))
2015        .with_argument(InputValue::new("offset", TypeRef::named("Int")));
2016    if columns {
2017        field = field.with_argument(InputValue::new(
2018            "order_by",
2019            TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(type_name)),
2020        ));
2021    }
2022    field.with_argument(InputValue::new(
2023        "where",
2024        TypeRef::named(crate::input::bool_exp::bool_exp_type_name(type_name)),
2025    ))
2026}
2027
2028/// One argument per primary key column, named and typed after the column
2029/// itself rather than assuming an integer `id`.
2030fn with_key_arguments<F: RootField>(
2031    field: F,
2032    schema_name: &str,
2033    table_name: &str,
2034    pk_columns: &[(String, String)],
2035    names: &crate::names::NameOverrides,
2036) -> F {
2037    let mut field = field;
2038    for (column, pg_type) in pk_columns {
2039        field = field.with_argument(InputValue::new(
2040            names
2041                .column(schema_name, table_name, column)
2042                .unwrap_or(column),
2043            TypeRef::named_nn(exposed_pk_argument_type(
2044                names,
2045                schema_name,
2046                table_name,
2047                column,
2048                pg_type,
2049            )),
2050        ));
2051    }
2052    field
2053}
2054
2055/// Create the Query type with all table query fields.
2056fn create_query_type(
2057    generated: &GeneratedSchema,
2058    root_name: &str,
2059    max_rows: Option<i64>,
2060    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2061    names: Arc<crate::names::NameOverrides>,
2062) -> Object {
2063    // Collected rather than added as they are built, so they can go on in name
2064    // order: that is the order Hasura answers introspection in, and it is the
2065    // order a client diffing two schemas, or a generator writing its types
2066    // out, gets a stable answer from.
2067    let mut roots: Vec<(String, Field)> = Vec::new();
2068
2069    for field in &generated.query_fields {
2070        let table_name = field.table_name.clone();
2071        let schema_name = field.schema_name.clone();
2072        let type_name = field.type_name.clone();
2073        let local_type_name = field.local_type_name.clone();
2074        let is_by_pk = field.is_by_pk;
2075        let pk_columns = field.pk_columns.clone();
2076        let return_type = graphql_type_ref(&field.return_type);
2077
2078        let spec_type_name = local_type_name.clone();
2079        let spec = Arc::new(QueryFieldSpec {
2080            schema_name,
2081            table_name,
2082            type_name,
2083            is_by_pk,
2084            pk_columns: pk_columns.clone(),
2085            max_rows,
2086            relationships: Arc::clone(&relationships),
2087            names: Arc::clone(&names),
2088            call: None,
2089        });
2090
2091        let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2092            let spec = Arc::clone(&spec);
2093            FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
2094        });
2095
2096        // Add standard query arguments
2097        gql_field = match is_by_pk {
2098            false => with_row_arguments(gql_field, &spec_type_name),
2099            true => with_key_arguments(
2100                gql_field,
2101                &field.schema_name,
2102                &field.table_name,
2103                &pk_columns,
2104                names.as_ref(),
2105            ),
2106        };
2107
2108        if let Some(desc) = &field.description {
2109            gql_field = gql_field.description(desc);
2110        }
2111
2112        // A table this role may count and not read has no row type, so the
2113        // list root that would answer with one is not built. The entry is
2114        // still here for the aggregate root below, which is what such a role
2115        // was granted.
2116        if field.rows {
2117            roots.push((field.name.clone(), gql_field));
2118        }
2119
2120        // The same rows, with numbers about them. Same arguments as the list
2121        // field, because `author_aggregate(where: ...)` counts the set the
2122        // filter describes, not the whole table.
2123        //
2124        // Not built at all for a role whose permission withholds it: counting
2125        // rows is a way of learning about them, so it is granted separately
2126        // from reading them and refused by absence rather than at execution.
2127        if !is_by_pk && field.aggregates {
2128            let agg_spec = Arc::new(AggregateSpec {
2129                schema_name: field.schema_name.clone(),
2130                table_name: field.table_name.clone(),
2131                type_name: field.type_name.clone(),
2132                max_rows,
2133                relationships: Arc::clone(&relationships),
2134                names: Arc::clone(&names),
2135                call: None,
2136            });
2137            let aggregate_field_name = field.aggregate_name.clone().unwrap_or_else(|| {
2138                crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2139            });
2140            let aggregate_field_name_for_sorting = aggregate_field_name.clone();
2141            let mut agg_field = Field::new(
2142                aggregate_field_name,
2143                TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2144                    &field.local_type_name,
2145                )),
2146                move |ctx| {
2147                    let agg_spec = Arc::clone(&agg_spec);
2148                    FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
2149                },
2150            );
2151            agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2152            agg_field = match field.aggregate_description.as_deref() {
2153                Some("") => agg_field,
2154                Some(given) => agg_field.description(given),
2155                None => agg_field.description(format!(
2156                    "fetch aggregated fields from the table: \"{}\"",
2157                    field.table_name
2158                )),
2159            };
2160            roots.push((aggregate_field_name_for_sorting, agg_field));
2161        }
2162    }
2163
2164    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2165    let mut query = Object::new(root_name);
2166    // A GraphQL object may not have no fields, so a schema that exposes no
2167    // table needs something on its query root. Hasura puts a placeholder there
2168    // and calls it this; a client that reaches it has nothing to read.
2169    if roots.is_empty() {
2170        return query.field(
2171            Field::new("no_queries_available", TypeRef::named("String"), |_| {
2172                FieldFuture::new(async move { Ok(None::<Value>) })
2173            })
2174            .description("There are no queries available to the current role."),
2175        );
2176    }
2177    for (_, field) in roots {
2178        query = query.field(field);
2179    }
2180
2181    query
2182}
2183
2184/// One document operator: its name, and the type its columns are given.
2185type JsonbOperator = (&'static str, fn() -> TypeRef);
2186
2187/// What a document column may be told to do, and what each is told with.
2188///
2189/// `_append` and `_prepend` take another document; `_delete_key` takes a key;
2190/// `_delete_elem` takes an index into an array; `_delete_at_path` takes the
2191/// path to what is being removed. The shapes are per operator rather than per
2192/// column, which is why one input type per operator says all of it.
2193const JSONB_OPERATORS: &[JsonbOperator] = &[
2194    ("_append", || TypeRef::named("jsonb")),
2195    ("_delete_at_path", || TypeRef::named_nn_list("String")),
2196    ("_delete_elem", || TypeRef::named("Int")),
2197    ("_delete_key", || TypeRef::named("String")),
2198    ("_prepend", || TypeRef::named("jsonb")),
2199];
2200
2201/// The name of the input holding one document operator's columns.
2202fn jsonb_operator_input(type_name: &str, operator: &str) -> String {
2203    format!("{}{}_input", type_name, operator)
2204}
2205
2206/// Add the operators an update may be written with.
2207///
2208/// All optional, and at least one required -- which GraphQL cannot express, so
2209/// the resolver says so instead of the schema. Making `_set` non-null would
2210/// have been expressible and wrong: an update that only increments a counter
2211/// never sends one.
2212fn with_update_operators(
2213    field: Field,
2214    base_name: &str,
2215    has_numeric: bool,
2216    has_jsonb: bool,
2217) -> Field {
2218    let mut field = field;
2219    // A table with no document column is not offered the document operators:
2220    // there is nothing there to append to.
2221    if has_jsonb {
2222        for (operator, _) in JSONB_OPERATORS {
2223            field = field.argument(InputValue::new(
2224                *operator,
2225                TypeRef::named(jsonb_operator_input(base_name, operator)),
2226            ));
2227        }
2228    }
2229    if has_numeric {
2230        field = field.argument(InputValue::new(
2231            "_inc",
2232            TypeRef::named(format!("{}_inc_input", base_name)),
2233        ));
2234    }
2235    field.argument(InputValue::new(
2236        "_set",
2237        TypeRef::named(format!("{}_set_input", base_name)),
2238    ))
2239}
2240
2241/// Create the Mutation type with all mutation fields.
2242/// Which columns a write may name, and so which of its inputs exist.
2243///
2244/// One definition, called both where the inputs are registered and where the
2245/// arguments naming them are declared. They used to agree by coincidence --
2246/// both read the object type's fields -- and stopped the moment a write input
2247/// began following its own permission. An argument naming a type nobody
2248/// registered is a schema async-graphql refuses to build, which costs a role
2249/// every field it has over the one it cannot use.
2250fn write_fields<'a>(
2251    object: &'a crate::schema::object::TableObjectType,
2252    names: &crate::names::NameOverrides,
2253    role: Option<&str>,
2254    verb: crate::role::Verb,
2255) -> Vec<&'a crate::schema::object::GraphQLField> {
2256    let allowed = role.and_then(|role| {
2257        names
2258            .permissions(&object.table.schema, &object.table.name, role)
2259            .and_then(|granted| granted.write_columns(verb))
2260    });
2261    object
2262        .writable_fields
2263        .iter()
2264        .filter(|field| {
2265            let column = table_column_for(names, &object.table, &field.name);
2266            // A column PostgreSQL generates always is not one a write may
2267            // name. Leaving it in the input type moved the refusal to the
2268            // database, which answers `cannot insert a non-DEFAULT value into
2269            // column "id"` and has by then forgotten which argument the value
2270            // came from. Out of the type, the walk over the request says
2271            // `field 'id' not found in type: 'author_insert_input'` and can
2272            // still point at it.
2273            object
2274                .table
2275                .get_column(column)
2276                .is_some_and(|c| !c.always_generated)
2277                && allowed.is_none_or(|set| set.allows(column))
2278        })
2279        .collect()
2280}
2281
2282/// Whether a type has an `_inc_input` and a set of document operator inputs.
2283fn update_inputs(
2284    object: &crate::schema::object::TableObjectType,
2285    names: &crate::names::NameOverrides,
2286    role: Option<&str>,
2287) -> (bool, bool) {
2288    let fields = write_fields(object, names, role, crate::role::Verb::Update);
2289    (
2290        fields
2291            .iter()
2292            .any(|f| crate::schema::aggregate::is_incrementable(&f.graphql_type)),
2293        fields
2294            .iter()
2295            .any(|f| matches!(&f.graphql_type, crate::types::GraphQLType::Json)),
2296    )
2297}
2298
2299fn create_mutation_type(
2300    generated: &GeneratedSchema,
2301    root_name: &str,
2302    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2303    type_names: Arc<HashMap<(String, String), String>>,
2304    names: Arc<crate::names::NameOverrides>,
2305    max_rows: Option<i64>,
2306    role: Option<&str>,
2307) -> Object {
2308    let mut mutation = Object::new(root_name);
2309    // In name order, for the reason the query root is: see there.
2310    let mut roots: Vec<(String, Field)> = Vec::new();
2311
2312    // Only a table with a unique constraint has a conflict to name, and only
2313    // those got the types for it.
2314    // Which tables have something to add to, and which have a document to
2315    // operate on. Read from what was registered above rather than worked out
2316    // again from the object types: the second answer is the one that can
2317    // drift, and drifting means an argument naming a type that is not there.
2318    let mut has_numeric_column: HashSet<String> = HashSet::new();
2319    let mut has_jsonb_column: HashSet<String> = HashSet::new();
2320    for object in generated.object_types.values() {
2321        let (numeric, jsonb) = update_inputs(object, &names, role);
2322        if numeric {
2323            has_numeric_column.insert(object.local_name.clone());
2324        }
2325        if jsonb {
2326            has_jsonb_column.insert(object.local_name.clone());
2327        }
2328    }
2329
2330    let has_conflict_target: HashSet<String> = generated
2331        .object_types
2332        .iter()
2333        .filter(|(_, object)| !object.table.unique_constraints.is_empty())
2334        .map(|(_, object)| object.local_name.clone())
2335        .collect();
2336
2337    for field in &generated.mutation_fields {
2338        let table_name = field.table_name.clone();
2339        let schema_name = field.schema_name.clone();
2340        let mutation_type = field.mutation_type;
2341        let pk_columns = field.pk_columns.clone();
2342        let return_type = graphql_type_ref(&field.return_type);
2343
2344        // The table's own type name, which is what its boolean expression is
2345        // named after. A bulk mutation returns `[author!]!` and a by-key one
2346        // returns `author`; both name the same table.
2347        let where_type = field.local_type_name.clone();
2348
2349        let resolver_pk_columns = pk_columns.clone();
2350        let field_relationships = Arc::clone(&relationships);
2351        let field_type_names = Arc::clone(&type_names);
2352        let field_names = Arc::clone(&names);
2353        let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2354            let table_name = table_name.clone();
2355            let schema_name = schema_name.clone();
2356            let pk_columns = resolver_pk_columns.clone();
2357            let relationships = Arc::clone(&field_relationships);
2358            let type_names = Arc::clone(&field_type_names);
2359            let names = Arc::clone(&field_names);
2360            FieldFuture::new(async move {
2361                resolve_mutation(
2362                    &ctx,
2363                    &schema_name,
2364                    &table_name,
2365                    mutation_type,
2366                    &pk_columns,
2367                    &relationships,
2368                    &type_names,
2369                    &names,
2370                    max_rows,
2371                )
2372                .await
2373            })
2374        });
2375
2376        // Add mutation-specific arguments.
2377        //
2378        // A by-PK mutation takes the key columns rather than a `where` object:
2379        // it is meant to address exactly one row, and accepting `where` made it
2380        // an ordinary bulk mutation that happened to return the first result.
2381        match mutation_type {
2382            MutationType::Insert | MutationType::InsertOne => {
2383                let insert_input = format!("{}_insert_input", where_type);
2384                gql_field = if mutation_type == MutationType::Insert {
2385                    gql_field.argument(InputValue::new(
2386                        "objects",
2387                        TypeRef::named_nn_list_nn(&insert_input),
2388                    ))
2389                } else {
2390                    // A single insert takes `object`, not a one-element
2391                    // `objects`.
2392                    gql_field.argument(InputValue::new("object", TypeRef::named_nn(&insert_input)))
2393                };
2394                if has_conflict_target.contains(&where_type) {
2395                    gql_field = gql_field.argument(InputValue::new(
2396                        "on_conflict",
2397                        TypeRef::named(format!("{}_on_conflict", where_type)),
2398                    ));
2399                }
2400            }
2401            MutationType::UpdateByPk => {
2402                gql_field = gql_field.argument(InputValue::new(
2403                    "pk_columns",
2404                    TypeRef::named_nn(format!("{}_pk_columns_input", where_type)),
2405                ));
2406                gql_field = with_update_operators(
2407                    gql_field,
2408                    &where_type,
2409                    has_numeric_column.contains(&where_type),
2410                    has_jsonb_column.contains(&where_type),
2411                );
2412            }
2413            MutationType::Update => {
2414                gql_field = gql_field.argument(InputValue::new(
2415                    "where",
2416                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2417                ));
2418                gql_field = with_update_operators(
2419                    gql_field,
2420                    &where_type,
2421                    has_numeric_column.contains(&where_type),
2422                    has_jsonb_column.contains(&where_type),
2423                );
2424            }
2425            MutationType::UpdateMany => {
2426                gql_field = gql_field.argument(InputValue::new(
2427                    "updates",
2428                    TypeRef::named_nn_list_nn(format!("{}_updates", where_type)),
2429                ));
2430            }
2431            MutationType::DeleteByPk => {
2432                for (col_name, pg_type) in &pk_columns {
2433                    gql_field = gql_field.argument(InputValue::new(
2434                        names
2435                            .column(&field.schema_name, &field.table_name, col_name)
2436                            .unwrap_or(col_name),
2437                        TypeRef::named_nn(exposed_pk_argument_type(
2438                            names.as_ref(),
2439                            &field.schema_name,
2440                            &field.table_name,
2441                            col_name,
2442                            pg_type,
2443                        )),
2444                    ));
2445                }
2446            }
2447            MutationType::Delete => {
2448                // Required, as it is on an update: a delete with no predicate
2449                // is a delete of the whole table, and this refused one at
2450                // execution while the schema said it was a query worth
2451                // writing. Saying so in the type is what Hasura does, and it
2452                // is what a client's own tooling can catch before the request
2453                // is sent.
2454                gql_field = gql_field.argument(InputValue::new(
2455                    "where",
2456                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2457                ));
2458            }
2459        }
2460
2461        if let Some(desc) = &field.description {
2462            gql_field = gql_field.description(desc);
2463        }
2464
2465        roots.push((field.name.clone(), gql_field));
2466    }
2467
2468    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2469    for (_, field) in roots {
2470        mutation = mutation.field(field);
2471    }
2472
2473    mutation
2474}
2475
2476/// Create the Subscription type: the query root, answered again on change.
2477///
2478/// A subscription here is a **live query**. It carries the same fields the
2479/// query root does, takes the same arguments, and answers with the same rows
2480/// -- the difference is that the answer is sent again whenever it stops being
2481/// true. That is the contract a client generated against Hasura expects, and
2482/// the reason its `subscription_root` is a mirror of its `query_root`.
2483///
2484/// What wakes it is this server's own: a trigger publishes on the changed
2485/// table and the query is read again, which costs nothing while nothing is
2486/// written and answers in milliseconds when something is. A trigger cannot
2487/// see everything a query can -- a view has none, an embedded row may live in
2488/// a table that carries none, and `where: {expires_at: {_lt: "now()"}}`
2489/// changes with no write at all -- so a slow refresh runs beside it and
2490/// closes exactly those gaps. Polling alone is what Hasura does, and it pays
2491/// for every subscriber every tick whether or not anything happened.
2492fn create_subscription_type(
2493    generated: &GeneratedSchema,
2494    root_name: &str,
2495    max_rows: Option<i64>,
2496    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2497    names: Arc<crate::names::NameOverrides>,
2498    refresh: std::time::Duration,
2499) -> Subscription {
2500    let mut roots: Vec<(String, SubscriptionField)> = Vec::new();
2501
2502    for field in &generated.query_fields {
2503        let spec_type_name = field.local_type_name.clone();
2504        let pk_columns = field.pk_columns.clone();
2505        let is_by_pk = field.is_by_pk;
2506        let return_type = graphql_type_ref(&field.return_type);
2507        let watched = vec![crate::subscription::table_channel_name(
2508            &field.schema_name,
2509            &field.table_name,
2510        )];
2511
2512        let spec = Arc::new(QueryFieldSpec {
2513            schema_name: field.schema_name.clone(),
2514            table_name: field.table_name.clone(),
2515            type_name: field.type_name.clone(),
2516            is_by_pk,
2517            pk_columns: pk_columns.clone(),
2518            max_rows,
2519            relationships: Arc::clone(&relationships),
2520            names: Arc::clone(&names),
2521            call: None,
2522        });
2523
2524        let mut gql_field = SubscriptionField::new(&field.name, return_type, move |ctx| {
2525            let spec = Arc::clone(&spec);
2526            let watched = watched.clone();
2527            SubscriptionFieldFuture::new(async move {
2528                let wake = wake_on(&ctx, &watched, refresh).await;
2529                Ok(live(ctx, LiveSource::Rows(spec), wake))
2530            })
2531        });
2532
2533        gql_field = match is_by_pk {
2534            false => with_row_arguments(gql_field, &spec_type_name),
2535            true => with_key_arguments(
2536                gql_field,
2537                &field.schema_name,
2538                &field.table_name,
2539                &pk_columns,
2540                names.as_ref(),
2541            ),
2542        };
2543        if let Some(desc) = &field.description {
2544            gql_field = gql_field.description(desc);
2545        }
2546        // No row type, no root that answers with one -- see the query root.
2547        if field.rows {
2548            roots.push((field.name.clone(), gql_field));
2549        }
2550
2551        // A live query mirrors the query root, so an aggregate the role may not
2552        // ask for once is not one it may ask for continuously either.
2553        if is_by_pk || !field.aggregates {
2554            continue;
2555        }
2556
2557        // The same rows, with numbers about them, watched the same way.
2558        let agg_spec = Arc::new(AggregateSpec {
2559            schema_name: field.schema_name.clone(),
2560            table_name: field.table_name.clone(),
2561            type_name: field.type_name.clone(),
2562            max_rows,
2563            relationships: Arc::clone(&relationships),
2564            names: Arc::clone(&names),
2565            call: None,
2566        });
2567        let agg_watched = vec![crate::subscription::table_channel_name(
2568            &field.schema_name,
2569            &field.table_name,
2570        )];
2571        let agg_name = field.aggregate_name.clone().unwrap_or_else(|| {
2572            crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2573        });
2574        let mut agg_field = SubscriptionField::new(
2575            agg_name.clone(),
2576            TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2577                &field.local_type_name,
2578            )),
2579            move |ctx| {
2580                let agg_spec = Arc::clone(&agg_spec);
2581                let watched = agg_watched.clone();
2582                SubscriptionFieldFuture::new(async move {
2583                    let wake = wake_on(&ctx, &watched, refresh).await;
2584                    Ok(live(ctx, LiveSource::Aggregate(agg_spec), wake))
2585                })
2586            },
2587        );
2588        agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2589        agg_field = match field.aggregate_description.as_deref() {
2590            Some("") => agg_field,
2591            Some(given) => agg_field.description(given),
2592            None => agg_field.description(format!(
2593                "fetch aggregated fields from the table: \"{}\"",
2594                field.table_name
2595            )),
2596        };
2597        roots.push((agg_name, agg_field));
2598    }
2599
2600    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2601    let mut subscription = Subscription::new(root_name);
2602    // A GraphQL object may not have no fields, and a schema that exposes no
2603    // table still has a subscription root if subscriptions are on.
2604    if roots.is_empty() {
2605        return subscription.field(SubscriptionField::new(
2606            "no_subscriptions_available",
2607            TypeRef::named("String"),
2608            |_| {
2609                SubscriptionFieldFuture::new(async move {
2610                    Ok(futures::stream::empty::<
2611                        Result<FieldValue, async_graphql::Error>,
2612                    >())
2613                })
2614            },
2615        ));
2616    }
2617    for (_, field) in roots {
2618        subscription = subscription.field(field);
2619    }
2620    subscription
2621}
2622
2623/// What tells a live query its answer may have changed.
2624///
2625/// Every notification on the tables it reads, and a slow tick beside them.
2626/// Yields once per wake and never ends -- a subscription lasts as long as the
2627/// client holds it open.
2628async fn wake_on(
2629    ctx: &ResolverContext<'_>,
2630    channels: &[String],
2631    refresh: std::time::Duration,
2632) -> futures::stream::BoxStream<'static, ()> {
2633    use futures::StreamExt;
2634
2635    let mut streams: Vec<futures::stream::BoxStream<'static, ()>> =
2636        Vec::with_capacity(channels.len() + 1);
2637    // A tick even where nothing is listening: a table with no trigger, a view,
2638    // and a predicate that changes with the clock all depend on it.
2639    streams.push(
2640        tokio_stream::wrappers::IntervalStream::new({
2641            let mut interval = tokio::time::interval(refresh);
2642            interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
2643            interval
2644        })
2645        .map(|_| ())
2646        .boxed(),
2647    );
2648
2649    if let Ok(broker_arc) = ctx.data::<Arc<RwLock<Option<NotifyBroker>>>>() {
2650        let guard = broker_arc.read().await;
2651        if let Some(broker) = guard.as_ref() {
2652            for channel in channels {
2653                // `subscribe_or_create` rather than `subscribe`: a table whose
2654                // trigger was never installed has no channel, and a live query
2655                // on it is answered by the refresh rather than refused.
2656                streams.push(
2657                    broker
2658                        .subscribe_or_create(channel)
2659                        .await
2660                        .map(|_| ())
2661                        .boxed(),
2662                );
2663            }
2664        }
2665    }
2666
2667    futures::stream::select_all(streams).boxed()
2668}
2669
2670/// What a live query reads each time it is woken.
2671///
2672/// An enum rather than a closure because the answer borrows the context it is
2673/// read from, and a closure returning a future that borrows its argument is
2674/// not something a bound can say.
2675enum LiveSource {
2676    Rows(Arc<QueryFieldSpec>),
2677    Aggregate(Arc<AggregateSpec>),
2678}
2679
2680/// Read one answer, and the value to compare it against the last one by.
2681async fn read_live<'a>(
2682    ctx: &ResolverContext<'a>,
2683    source: &LiveSource,
2684) -> Result<(serde_json::Value, Option<FieldValue<'a>>), async_graphql::Error> {
2685    match source {
2686        LiveSource::Rows(spec) => {
2687            let rows = query_rows(ctx, spec).await?;
2688            Ok((
2689                serde_json::Value::Array(rows.clone()),
2690                rows_as_field_value(rows, spec.is_by_pk),
2691            ))
2692        }
2693        LiveSource::Aggregate(spec) => {
2694            let value = aggregate_value(ctx, spec).await?;
2695            let seen = serde_json::to_value(&value).unwrap_or_default();
2696            Ok((seen, Some(FieldValue::value(value))))
2697        }
2698    }
2699}
2700
2701/// A live query: the answer now, and the answer again each time it changes.
2702///
2703/// The first item is sent as soon as the client subscribes, which is what
2704/// makes this a query rather than a feed -- there is no window in which the
2705/// client knows nothing. After that, `answer` is read again on every wake and
2706/// sent only when what came back is not what was sent last: a write that
2707/// changes a row the subscription does not select is a wake and not a message.
2708fn live<'a>(
2709    ctx: ResolverContext<'a>,
2710    source: LiveSource,
2711    wake: futures::stream::BoxStream<'static, ()>,
2712) -> impl futures::Stream<Item = Result<FieldValue<'a>, async_graphql::Error>> + Send + 'a {
2713    use futures::StreamExt;
2714
2715    struct Live<'a> {
2716        ctx: ResolverContext<'a>,
2717        source: LiveSource,
2718        wake: futures::stream::BoxStream<'static, ()>,
2719        sent: Option<serde_json::Value>,
2720        started: bool,
2721    }
2722
2723    futures::stream::unfold(
2724        Live {
2725            ctx,
2726            source,
2727            wake,
2728            sent: None,
2729            started: false,
2730        },
2731        |mut live: Live<'a>| async move {
2732            loop {
2733                if live.started {
2734                    live.wake.next().await?;
2735                } else {
2736                    live.started = true;
2737                }
2738                let read = read_live(&live.ctx, &live.source).await;
2739                match read {
2740                    // An error ends the subscription: the client asked
2741                    // something that cannot be answered, and answering it
2742                    // again on the next tick would only repeat that.
2743                    Err(e) => return Some((Err(e), live)),
2744                    Ok((seen, value)) => {
2745                        if live.sent.as_ref() == Some(&seen) {
2746                            continue;
2747                        }
2748                        live.sent = Some(seen);
2749                        let value = value.unwrap_or_else(|| FieldValue::value(Value::Null));
2750                        return Some((Ok(value), live));
2751                    }
2752                }
2753            }
2754        },
2755    )
2756    .take_while(|item| futures::future::ready(item.is_ok()))
2757}
2758
2759/// Everything a query field's resolver needs about the field it serves.
2760struct QueryFieldSpec {
2761    schema_name: String,
2762    table_name: String,
2763    type_name: String,
2764    is_by_pk: bool,
2765    pk_columns: Vec<(String, String)>,
2766    max_rows: Option<i64>,
2767    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2768    names: Arc<crate::names::NameOverrides>,
2769    /// Set where the rows come from a function rather than from the table
2770    /// itself. Everything else about reading them is the same.
2771    call: Option<Arc<FunctionCall>>,
2772}
2773
2774/// Everything `_entities` needs to resolve a representation to a table row.
2775#[derive(Clone)]
2776struct EntityLookup {
2777    schema_name: String,
2778    table_name: String,
2779    type_name: String,
2780    key_columns: Vec<EntityKeyColumn>,
2781    row_column_types: HashMap<String, String>,
2782}
2783
2784#[derive(Clone)]
2785struct EntityKeyColumn {
2786    field_name: String,
2787    column_name: String,
2788    pg_type: String,
2789    explicit_id: bool,
2790}
2791
2792/// The federation entity resolver's immutable schema view.
2793struct EntityResolverState {
2794    entities: HashMap<String, EntityLookup>,
2795    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2796    names: Arc<crate::names::NameOverrides>,
2797    max_rows: Option<i64>,
2798}
2799
2800impl EntityResolverState {
2801    fn new(
2802        generated: &GeneratedSchema,
2803        relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2804        names: Arc<crate::names::NameOverrides>,
2805        max_rows: Option<i64>,
2806    ) -> Self {
2807        let mut entities = HashMap::new();
2808        for (type_name, entity) in &generated.federation_entities {
2809            let Some(object) = generated.object_types.get(type_name) else {
2810                continue;
2811            };
2812            let mut key_columns = Vec::with_capacity(entity.key_fields.len());
2813            for field_name in &entity.key_fields {
2814                let column_name = names
2815                    .column_source(&object.table.schema, &object.table.name, field_name)
2816                    .unwrap_or(field_name);
2817                let Some(column) = object.table.get_column(column_name) else {
2818                    continue;
2819                };
2820                key_columns.push(EntityKeyColumn {
2821                    field_name: field_name.clone(),
2822                    column_name: column.name.clone(),
2823                    pg_type: column.nominal_type.clone(),
2824                    explicit_id: matches!(
2825                        names.column_type(&object.table.schema, &object.table.name, &column.name),
2826                        Some(crate::types::GraphQLType::Id)
2827                    ),
2828                });
2829            }
2830            if key_columns.len() != entity.key_fields.len() {
2831                continue;
2832            }
2833            entities.insert(
2834                type_name.clone(),
2835                EntityLookup {
2836                    schema_name: object.table.schema.clone(),
2837                    table_name: object.table.name.clone(),
2838                    type_name: type_name.clone(),
2839                    key_columns,
2840                    row_column_types: exposed_column_types(&object.table, names.as_ref()),
2841                },
2842            );
2843        }
2844        Self {
2845            entities,
2846            relationships,
2847            names,
2848            max_rows,
2849        }
2850    }
2851}
2852
2853struct EntityRepresentation {
2854    index: usize,
2855    type_name: String,
2856    values: serde_json::Map<String, serde_json::Value>,
2857}
2858
2859/// Fields selected for one concrete member of the federation `_Entity` union.
2860///
2861/// `SelectionField::selection_set()` expands every fragment but does not apply
2862/// its type condition. Keep a parallel applicability list while walking the
2863/// raw selection tree, then use it to retain only the fields meant for this
2864/// entity type.
2865fn entity_field_applicability(
2866    selection_set: &SelectionSet,
2867    fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2868    type_name: &str,
2869) -> Vec<bool> {
2870    fn condition_matches(condition: Option<&Positioned<TypeCondition>>, type_name: &str) -> bool {
2871        condition.is_none_or(|condition| {
2872            let condition = condition.node.on.node.as_str();
2873            condition == type_name || condition == "_Entity"
2874        })
2875    }
2876
2877    fn collect(
2878        selection_set: &SelectionSet,
2879        fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2880        type_name: &str,
2881        parent_matches: bool,
2882        applicability: &mut Vec<bool>,
2883    ) {
2884        for selection in &selection_set.items {
2885            match &selection.node {
2886                Selection::Field(_) => applicability.push(parent_matches),
2887                Selection::FragmentSpread(spread) => {
2888                    let Some(fragment) = fragments.get(&spread.node.fragment_name.node) else {
2889                        continue;
2890                    };
2891                    collect(
2892                        &fragment.node.selection_set.node,
2893                        fragments,
2894                        type_name,
2895                        parent_matches
2896                            && condition_matches(Some(&fragment.node.type_condition), type_name),
2897                        applicability,
2898                    );
2899                }
2900                Selection::InlineFragment(fragment) => collect(
2901                    &fragment.node.selection_set.node,
2902                    fragments,
2903                    type_name,
2904                    parent_matches
2905                        && condition_matches(fragment.node.type_condition.as_ref(), type_name),
2906                    applicability,
2907                ),
2908            }
2909        }
2910    }
2911
2912    let mut applicability = Vec::new();
2913    collect(
2914        selection_set,
2915        fragments,
2916        type_name,
2917        true,
2918        &mut applicability,
2919    );
2920    applicability
2921}
2922
2923fn entity_selection_fields<'ctx, 'a>(
2924    ctx: &'ctx ResolverContext<'a>,
2925    type_name: &str,
2926) -> Vec<SelectionField<'ctx>> {
2927    let applicability = entity_field_applicability(
2928        &ctx.ctx.item.node.selection_set.node,
2929        &ctx.ctx.query_env.fragments,
2930        type_name,
2931    );
2932    let fields: Vec<_> = ctx.field().selection_set().collect();
2933    debug_assert_eq!(fields.len(), applicability.len());
2934    fields
2935        .into_iter()
2936        .zip(applicability)
2937        .filter_map(|(field, applies)| applies.then_some(field))
2938        .collect()
2939}
2940
2941async fn resolve_entities<'a>(
2942    ctx: &ResolverContext<'a>,
2943    state: &EntityResolverState,
2944) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
2945    let representations = ctx.args.try_get("representations")?.list()?;
2946    let mut grouped: HashMap<String, Vec<EntityRepresentation>> = HashMap::new();
2947    let mut count = 0;
2948    for (index, representation) in representations.iter().enumerate() {
2949        count += 1;
2950        let value = accessor_to_json(&representation);
2951        let serde_json::Value::Object(values) = value else {
2952            return Err(async_graphql::Error::new(
2953                "entity representation must be an object",
2954            ));
2955        };
2956        let type_name = values
2957            .get("__typename")
2958            .and_then(serde_json::Value::as_str)
2959            .ok_or_else(|| {
2960                async_graphql::Error::new("entity representation is missing __typename")
2961            })?
2962            .to_string();
2963        grouped
2964            .entry(type_name.clone())
2965            .or_default()
2966            .push(EntityRepresentation {
2967                index,
2968                type_name,
2969                values,
2970            });
2971    }
2972
2973    let mut resolved: Vec<Option<FieldValue<'a>>> =
2974        std::iter::repeat_with(|| None).take(count).collect();
2975    for (type_name, representations) in grouped {
2976        let lookup = state.entities.get(&type_name).ok_or_else(|| {
2977            async_graphql::Error::new(format!("unknown federation entity \"{}\"", type_name))
2978        })?;
2979        let rows = resolve_entity_group(ctx, lookup, &representations, state).await?;
2980        for (ordinality, representation) in representations.into_iter().enumerate() {
2981            resolved[representation.index] = Some(match &rows[ordinality] {
2982                Some(row) => FieldValue::value(json_to_value(row.clone()))
2983                    .with_type(representation.type_name),
2984                None => FieldValue::NULL,
2985            });
2986        }
2987    }
2988
2989    Ok(Some(FieldValue::list(resolved.into_iter().map(|value| {
2990        value.expect("every representation was resolved or errored")
2991    }))))
2992}
2993
2994async fn resolve_entity_group(
2995    ctx: &ResolverContext<'_>,
2996    lookup: &EntityLookup,
2997    representations: &[EntityRepresentation],
2998    state: &EntityResolverState,
2999) -> Result<Vec<Option<serde_json::Value>>, async_graphql::Error> {
3000    if representations.is_empty() {
3001        return Ok(Vec::new());
3002    }
3003
3004    let pool = ctx.data::<PgPool>()?;
3005    let gql_ctx = ctx.data::<GraphQLContext>()?;
3006    let mut bound_values = Vec::new();
3007    let mut value_rows = Vec::with_capacity(representations.len());
3008    for (ordinality, representation) in representations.iter().enumerate() {
3009        let normalized_values = normalized_entity_key_values(
3010            &representation.values,
3011            &lookup.key_columns,
3012            &lookup.type_name,
3013        )?;
3014        let mut values = Vec::with_capacity(lookup.key_columns.len() + 1);
3015        for (key_column, value) in lookup.key_columns.iter().zip(normalized_values) {
3016            values.push(format!(
3017                "${}::{}",
3018                bound_values.len() + 1,
3019                key_column.pg_type
3020            ));
3021            bound_values.push(value);
3022        }
3023        values.push(ordinality.to_string());
3024        value_rows.push(format!("({})", values.join(", ")));
3025    }
3026
3027    let representation_alias = "pgrst_entity_representation";
3028    let ordinality_column = "pgrst_entity_ordinality";
3029    let key_aliases: Vec<String> = (0..lookup.key_columns.len())
3030        .map(|index| format!("pgrst_entity_key_{index}"))
3031        .collect();
3032    let mut representation_columns: Vec<String> = key_aliases
3033        .iter()
3034        .map(|column| postrust_sql::escape_ident(column))
3035        .collect();
3036    representation_columns.push(postrust_sql::escape_ident(ordinality_column));
3037    let representations_sql = format!(
3038        "(VALUES {}) AS {} ({})",
3039        value_rows.join(", "),
3040        postrust_sql::escape_ident(representation_alias),
3041        representation_columns.join(", ")
3042    );
3043    let key_predicate = lookup
3044        .key_columns
3045        .iter()
3046        .zip(&key_aliases)
3047        .map(|(key_column, key_alias)| {
3048            format!(
3049                "{}.{} = {}.{}",
3050                postrust_sql::escape_ident(READ_ROW),
3051                postrust_sql::escape_ident(&key_column.column_name),
3052                postrust_sql::escape_ident(representation_alias),
3053                postrust_sql::escape_ident(key_alias)
3054            )
3055        })
3056        .collect::<Vec<_>>()
3057        .join(" AND ");
3058    let mut where_sql = format!(" WHERE ({key_predicate})");
3059
3060    let permission = permission_predicate(
3061        &gql_ctx.caller(),
3062        state.names.as_ref(),
3063        &lookup.schema_name,
3064        &lookup.table_name,
3065    )?;
3066    if let Some(predicate) = permission {
3067        let guard = gql_ctx
3068            .schema_cache
3069            .get()
3070            .await
3071            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3072        let cache = guard
3073            .as_ref()
3074            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3075        let scope = WhereScope::table(
3076            &lookup.schema_name,
3077            &lookup.table_name,
3078            &lookup.type_name,
3079            state.names.as_ref(),
3080        )
3081        .under_alias(READ_ROW)
3082        .with_resolution(cache, state.relationships.as_ref())
3083        .for_caller(gql_ctx.caller());
3084        let (filter_sql, filter_values) =
3085            build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3086        if !filter_sql.is_empty() {
3087            where_sql = format!(
3088                "{} AND ({})",
3089                where_sql,
3090                filter_sql.trim_start_matches("WHERE ")
3091            );
3092            bound_values.extend(filter_values);
3093        }
3094    }
3095
3096    let source = format!(
3097        "{}.{} AS {}",
3098        postrust_sql::escape_ident(&lookup.schema_name),
3099        postrust_sql::escape_ident(&lookup.table_name),
3100        postrust_sql::escape_ident(READ_ROW)
3101    );
3102    let source_rows = format!(
3103        "SELECT {}.* FROM {}{}",
3104        postrust_sql::escape_ident(READ_ROW),
3105        source,
3106        where_sql
3107    );
3108    let mut projection = {
3109        let guard = gql_ctx
3110            .schema_cache
3111            .get()
3112            .await
3113            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3114        guard
3115            .as_ref()
3116            .and_then(|cache| {
3117                cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3118                    &lookup.schema_name,
3119                    &lookup.table_name,
3120                ))
3121            })
3122            .and_then(|table| rename_projection(table, "src", state.names.as_ref()))
3123            .unwrap_or_else(|| "src.*".to_string())
3124    };
3125    let projected_rows = {
3126        let guard = gql_ctx
3127            .schema_cache
3128            .get()
3129            .await
3130            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3131        let cache = guard
3132            .as_ref()
3133            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3134        let table = cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3135            &lookup.schema_name,
3136            &lookup.table_name,
3137        ));
3138        let selected_fields = entity_selection_fields(ctx, &lookup.type_name);
3139        let mut param_idx = bound_values.len() + 1;
3140        let computed = match table {
3141            Some(table) => computed_projections(
3142                table,
3143                selected_fields.iter().copied(),
3144                "src",
3145                state.names.as_ref(),
3146                cache,
3147                &mut param_idx,
3148                &mut bound_values,
3149            )?,
3150            None => Vec::new(),
3151        };
3152        let embeds = build_embed_expressions(
3153            &gql_ctx.caller(),
3154            cache,
3155            state.relationships.as_ref(),
3156            &lookup.type_name,
3157            "src",
3158            selected_fields,
3159            state.max_rows,
3160            &mut 0,
3161            &mut param_idx,
3162            &mut bound_values,
3163            state.names.as_ref(),
3164        )?;
3165        for expression in &computed {
3166            projection.push_str(", ");
3167            projection.push_str(expression);
3168        }
3169        for (field_name, expression) in &embeds {
3170            projection.push_str(", ");
3171            projection.push_str(expression);
3172            projection.push_str(" AS ");
3173            projection.push_str(&postrust_sql::escape_ident(field_name));
3174        }
3175        format!("SELECT {} FROM ({}) AS src", projection, source_rows)
3176    };
3177    // PostgreSQL decides whether an entity key matches. Carry the input's
3178    // ordinal through that same query instead of reconstructing a key from
3179    // row JSON: PostgreSQL-equivalent spellings (UUID case, citext case and
3180    // timestamptz offsets, for example) need to resolve to the row it found.
3181    let sql = format!(
3182        "SELECT json_build_array({}.{}, {}) FROM {} CROSS JOIN LATERAL ({}) AS t",
3183        postrust_sql::escape_ident(representation_alias),
3184        postrust_sql::escape_ident(ordinality_column),
3185        row_json("t", &lookup.row_column_types),
3186        representations_sql,
3187        projected_rows,
3188    );
3189    let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3190    let rows = execute_query_on(&mut tx, &sql, &bound_values).await?;
3191    tx.commit().await?;
3192
3193    let mut by_ordinal = std::iter::repeat_with(|| None)
3194        .take(representations.len())
3195        .collect::<Vec<_>>();
3196    for result in rows {
3197        let serde_json::Value::Array(mut pair) = result else {
3198            return Err(async_graphql::Error::new(
3199                "entity query returned an invalid result",
3200            ));
3201        };
3202        if pair.len() != 2 {
3203            return Err(async_graphql::Error::new(
3204                "entity query returned an invalid result",
3205            ));
3206        }
3207        let row = pair.pop().expect("the result pair has two values");
3208        let ordinality = pair
3209            .pop()
3210            .and_then(|value| value.as_u64())
3211            .and_then(|value| usize::try_from(value).ok())
3212            .filter(|value| *value < by_ordinal.len())
3213            .ok_or_else(|| async_graphql::Error::new("entity query returned an invalid ordinal"))?;
3214        by_ordinal[ordinality] = Some(row);
3215    }
3216    Ok(by_ordinal)
3217}
3218
3219/// Validate an entity key and normalize explicit GraphQL `ID` overrides to
3220/// their integer, text or UUID storage type. `_Any` deliberately skips the
3221/// field-level checks ordinary GraphQL arguments receive.
3222fn normalized_entity_key_values(
3223    values: &serde_json::Map<String, serde_json::Value>,
3224    key_columns: &[EntityKeyColumn],
3225    type_name: &str,
3226) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
3227    let mut key = Vec::with_capacity(key_columns.len());
3228    for key_column in key_columns {
3229        let value = values.get(&key_column.field_name).ok_or_else(|| {
3230            async_graphql::Error::new(format!(
3231                "entity representation for \"{}\" is missing key field \"{}\"",
3232                type_name, key_column.field_name
3233            ))
3234        })?;
3235        key.push(normalize_entity_key_value(type_name, key_column, value)?);
3236    }
3237    Ok(key)
3238}
3239
3240fn normalize_entity_key_value(
3241    type_name: &str,
3242    key_column: &EntityKeyColumn,
3243    value: &serde_json::Value,
3244) -> Result<serde_json::Value, async_graphql::Error> {
3245    let invalid = |expected: &str| {
3246        async_graphql::Error::new(format!(
3247            "entity representation for \"{}\" has invalid key field \"{}\": expected {}, found {}",
3248            type_name,
3249            key_column.field_name,
3250            expected,
3251            json_value_kind(value)
3252        ))
3253    };
3254
3255    if key_column.explicit_id {
3256        return normalize_graphql_id(value, &key_column.pg_type).ok_or_else(|| {
3257            invalid(&format!(
3258                "an ID compatible with PostgreSQL type \"{}\"",
3259                key_column.pg_type
3260            ))
3261        });
3262    }
3263
3264    let pg_type = key_column.pg_type.trim().to_ascii_lowercase();
3265    let normalized = if matches!(pg_type.as_str(), "int8" | "bigint") {
3266        match value {
3267            serde_json::Value::Number(number) => number.as_i64(),
3268            serde_json::Value::String(text) => text.parse::<i64>().ok(),
3269            _ => None,
3270        }
3271        .map(serde_json::Value::from)
3272    } else if integer_bounds(&pg_type).is_some() {
3273        value.as_i64().and_then(|integer| {
3274            let (min, max) = integer_bounds(&pg_type)?;
3275            (min..=max)
3276                .contains(&integer)
3277                .then(|| serde_json::Value::from(integer))
3278        })
3279    } else if matches!(
3280        pg_type.as_str(),
3281        "float4" | "float8" | "real" | "double precision"
3282    ) {
3283        value
3284            .as_f64()
3285            .and_then(serde_json::Number::from_f64)
3286            .map(serde_json::Value::Number)
3287    } else if matches!(pg_type.as_str(), "numeric" | "decimal") {
3288        match value {
3289            serde_json::Value::Number(number) => {
3290                Some(serde_json::Value::String(number.to_string()))
3291            }
3292            serde_json::Value::String(text) if text.parse::<sqlx::types::BigDecimal>().is_ok() => {
3293                Some(serde_json::Value::String(text.clone()))
3294            }
3295            _ => None,
3296        }
3297    } else if matches!(pg_type.as_str(), "bool" | "boolean") {
3298        value.as_bool().map(serde_json::Value::Bool)
3299    } else if matches!(pg_type.as_str(), "json" | "jsonb") {
3300        Some(value.clone())
3301    } else if is_array_type(&pg_type) {
3302        value.is_array().then(|| value.clone())
3303    } else {
3304        value
3305            .as_str()
3306            .map(|text| serde_json::Value::String(text.to_string()))
3307    };
3308
3309    normalized.ok_or_else(|| {
3310        invalid(&format!(
3311            "a value compatible with PostgreSQL type \"{}\"",
3312            key_column.pg_type
3313        ))
3314    })
3315}
3316
3317fn normalize_graphql_id(value: &serde_json::Value, pg_type: &str) -> Option<serde_json::Value> {
3318    if integer_bounds(pg_type).is_some() {
3319        let integer = match value {
3320            serde_json::Value::Number(number) => number.as_i64(),
3321            serde_json::Value::String(text) => text.parse::<i64>().ok(),
3322            _ => None,
3323        }?;
3324        let (min, max) = integer_bounds(pg_type)?;
3325        return (min..=max)
3326            .contains(&integer)
3327            .then(|| serde_json::Value::from(integer));
3328    }
3329    if pg_type.eq_ignore_ascii_case("uuid") {
3330        return normalize_uuid(value).map(serde_json::Value::String);
3331    }
3332    if is_text_type(pg_type) {
3333        return match value {
3334            serde_json::Value::String(s) => Some(serde_json::Value::String(s.clone())),
3335            serde_json::Value::Number(n) if n.is_i64() || n.is_u64() => {
3336                Some(serde_json::Value::String(n.to_string()))
3337            }
3338            _ => None,
3339        };
3340    }
3341    None
3342}
3343
3344fn integer_bounds(pg_type: &str) -> Option<(i64, i64)> {
3345    match pg_type.trim().to_ascii_lowercase().as_str() {
3346        "int2" | "smallint" => Some((i16::MIN.into(), i16::MAX.into())),
3347        "int4" | "int" | "integer" => Some((i32::MIN.into(), i32::MAX.into())),
3348        "int8" | "bigint" => Some((i64::MIN, i64::MAX)),
3349        _ => None,
3350    }
3351}
3352
3353fn is_text_type(pg_type: &str) -> bool {
3354    matches!(
3355        pg_type.trim().to_ascii_lowercase().as_str(),
3356        "text" | "varchar" | "character varying" | "char" | "character" | "bpchar" | "citext"
3357    )
3358}
3359
3360fn normalize_uuid(value: &serde_json::Value) -> Option<String> {
3361    sqlx::types::Uuid::parse_str(value.as_str()?)
3362        .ok()
3363        .map(|uuid| uuid.to_string())
3364}
3365
3366fn json_value_kind(value: &serde_json::Value) -> &'static str {
3367    match value {
3368        serde_json::Value::Null => "null",
3369        serde_json::Value::Bool(_) => "a boolean",
3370        serde_json::Value::Number(_) => "a number",
3371        serde_json::Value::String(_) => "a string",
3372        serde_json::Value::Array(_) => "an array",
3373        serde_json::Value::Object(_) => "an object",
3374    }
3375}
3376
3377/// Add the root fields for functions that answer with rows of a table.
3378///
3379/// The same arguments a table's own root field takes, because the rows are the
3380/// same rows: what the function adds is `args`, which is where its own
3381/// arguments go so that a parameter called `limit` cannot shadow the one that
3382/// pages the result.
3383fn add_function_fields(
3384    mut root: Object,
3385    generated: &GeneratedSchema,
3386    volatile: bool,
3387    max_rows: Option<i64>,
3388    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3389    names: Arc<crate::names::NameOverrides>,
3390) -> Object {
3391    // The names the root already carries, from the tables. A function may be
3392    // named after one -- `create function t() returns setof t` is an ordinary
3393    // way to write a parameterised view -- and `Object::field` *panics* on a
3394    // duplicate, so an unguarded collision is a server that will not start.
3395    let taken: HashSet<&str> = if volatile {
3396        generated
3397            .mutation_fields
3398            .iter()
3399            .map(|f| f.name.as_str())
3400            .collect()
3401    } else {
3402        generated
3403            .query_fields
3404            .iter()
3405            .map(|f| f.name.as_str())
3406            .collect()
3407    };
3408
3409    for function in &generated.function_fields {
3410        if function.volatile != volatile {
3411            continue;
3412        }
3413        if taken.contains(function.name.as_str()) {
3414            tracing::warn!(
3415                "not exposing {}.{}({}) -- a table already has a root field \
3416                 called {}",
3417                function.schema_name,
3418                function.function_name,
3419                function.argument_signature(),
3420                function.name,
3421            );
3422            continue;
3423        }
3424        let spec = Arc::new(QueryFieldSpec {
3425            schema_name: function.returns_table.0.clone(),
3426            table_name: function.returns_table.1.clone(),
3427            type_name: function.returns.clone(),
3428            is_by_pk: false,
3429            pk_columns: Vec::new(),
3430            max_rows,
3431            relationships: Arc::clone(&relationships),
3432            names: Arc::clone(&names),
3433            call: Some(Arc::new(FunctionCall {
3434                schema: function.schema_name.clone(),
3435                name: function.function_name.clone(),
3436                arguments: function.arguments.clone(),
3437                session_argument: function.session_argument.clone(),
3438            })),
3439        });
3440
3441        let mut field = Field::new(
3442            &function.name,
3443            TypeRef::named_nn_list_nn(&function.returns),
3444            move |ctx| {
3445                let spec = Arc::clone(&spec);
3446                FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
3447            },
3448        );
3449        if !function.arguments.is_empty() {
3450            field = field.argument(InputValue::new(
3451                "args",
3452                TypeRef::named_nn(format!("{}_args", function.name)),
3453            ));
3454        }
3455        field = field
3456            .argument(InputValue::new(
3457                "where",
3458                TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
3459                    &function.local_returns,
3460                )),
3461            ))
3462            .argument(InputValue::new(
3463                "order_by",
3464                TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
3465                    &function.local_returns,
3466                )),
3467            ))
3468            .argument(InputValue::new(
3469                "distinct_on",
3470                TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
3471                    &function.local_returns,
3472                )),
3473            ))
3474            .argument(InputValue::new("limit", TypeRef::named("Int")))
3475            .argument(InputValue::new("offset", TypeRef::named("Int")));
3476        if let Some(description) = &function.description {
3477            field = field.description(description);
3478        }
3479        root = root.field(field);
3480
3481        // The same rows, with numbers about them. Every table root has one and
3482        // so does every function root: `search_tracks_aggregate(args: {…})`
3483        // counts what `search_tracks(args: {…})` would have answered with,
3484        // which means calling the function and taking the same arguments.
3485        let agg_spec = Arc::new(AggregateSpec {
3486            schema_name: function.returns_table.0.clone(),
3487            table_name: function.returns_table.1.clone(),
3488            type_name: function.returns.clone(),
3489            max_rows,
3490            relationships: Arc::clone(&relationships),
3491            names: Arc::clone(&names),
3492            call: Some(Arc::new(FunctionCall {
3493                schema: function.schema_name.clone(),
3494                name: function.function_name.clone(),
3495                arguments: function.arguments.clone(),
3496                session_argument: function.session_argument.clone(),
3497            })),
3498        });
3499        let mut aggregate = Field::new(
3500            format!("{}_aggregate", function.name),
3501            TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
3502                &function.local_returns,
3503            )),
3504            move |ctx| {
3505                let agg_spec = Arc::clone(&agg_spec);
3506                FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
3507            },
3508        );
3509        if !function.arguments.is_empty() {
3510            aggregate = aggregate.argument(InputValue::new(
3511                "args",
3512                TypeRef::named_nn(format!("{}_args", function.name)),
3513            ));
3514        }
3515        aggregate = with_row_arguments(aggregate, &function.local_returns).description(format!(
3516            "fetch aggregated fields from the table: \"{}\"",
3517            function.returns_table.1
3518        ));
3519        root = root.field(aggregate);
3520    }
3521    root
3522}
3523
3524/// The FROM entry for a root field that calls a function.
3525///
3526/// Aliased as the table the function answers with rows of, so a filter or an
3527/// ordering written against that table's columns finds them: without it the
3528/// FROM clause names the function and `article.score` is a table nobody
3529/// mentioned. Whatever the caller put under `args` is bound here, which is why
3530/// it takes the parameter list.
3531fn function_source(
3532    ctx: &ResolverContext<'_>,
3533    call: &FunctionCall,
3534    alias: &str,
3535    bound_values: &mut Vec<serde_json::Value>,
3536) -> Result<String, async_graphql::Error> {
3537    let given = ctx
3538        .args
3539        .try_get("args")
3540        .ok()
3541        .map(|v| accessor_to_json(&v))
3542        .unwrap_or_else(|| serde_json::json!({}));
3543    let mut passed = Vec::new();
3544    for (name, _, required) in &call.arguments {
3545        match given.get(name) {
3546            None if *required => {
3547                return Err(async_graphql::Error::new(format!(
3548                    "{} needs the argument \"{}\"",
3549                    call.name, name
3550                )))
3551            }
3552            // An argument left out is left out, so the function's own default
3553            // applies. Passing null instead would override it.
3554            None => continue,
3555            Some(value) => {
3556                // Named notation, so the arguments a client did give land on
3557                // the parameters it meant regardless of order. A GraphQL Int
3558                // binds as a bigint, and `f(article_id => bigint)` is not
3559                // `f(integer)` -- the function is looked up by the types of
3560                // what it was handed, so what it was handed has to be the
3561                // types it declares.
3562                let (_, pg_type, _) = call
3563                    .arguments
3564                    .iter()
3565                    .find(|(argument, _, _)| argument == name)
3566                    .expect("iterating the arguments");
3567                passed.push(format!(
3568                    "{} => ${}::{}",
3569                    postrust_sql::escape_ident(name),
3570                    bound_values.len() + 1,
3571                    pg_type
3572                ));
3573                bound_values.push(value.clone());
3574            }
3575        }
3576    }
3577    // The session argument is not the client's to send: it says who is
3578    // asking, and a caller that could write it could name any identity it
3579    // liked. It is filled here, from the verified token.
3580    if let Some(session) = &call.session_argument {
3581        passed.push(format!(
3582            "{} => {}",
3583            postrust_sql::escape_ident(session),
3584            SESSION_ARGUMENT
3585        ));
3586    }
3587    Ok(format!(
3588        "{}.{}({}) AS {}",
3589        postrust_sql::escape_ident(&call.schema),
3590        postrust_sql::escape_ident(&call.name),
3591        passed.join(", "),
3592        postrust_sql::escape_ident(alias)
3593    ))
3594}
3595
3596/// A function a root field calls instead of reading a table.
3597struct FunctionCall {
3598    schema: String,
3599    name: String,
3600    arguments: Vec<(String, String, bool)>,
3601    /// The parameter filled from the caller's session rather than from the
3602    /// request. See [`crate::schema::FunctionField::session_argument`].
3603    session_argument: Option<String>,
3604}
3605
3606/// Everything an aggregate field's resolver needs.
3607struct AggregateSpec {
3608    schema_name: String,
3609    table_name: String,
3610    type_name: String,
3611    max_rows: Option<i64>,
3612    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3613    names: Arc<crate::names::NameOverrides>,
3614    /// Set where the rows come from a function rather than from the table
3615    /// itself, exactly as it is on [`QueryFieldSpec`]. Counting the rows a
3616    /// function answers with means calling it.
3617    call: Option<Arc<FunctionCall>>,
3618}
3619
3620/// Resolve an aggregate field.
3621///
3622/// One pass over the selection decides what SQL to write: a client asking only
3623/// for `count` should not pay for the rows, and one asking only for `nodes`
3624/// should not pay for a second scan. Both halves read from the same filtered
3625/// subquery, so `aggregate` describes exactly the set `nodes` came from.
3626async fn resolve_aggregate<'a>(
3627    ctx: &ResolverContext<'a>,
3628    spec: &AggregateSpec,
3629) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3630    Ok(Some(FieldValue::value(aggregate_value(ctx, spec).await?)))
3631}
3632
3633/// The numbers an aggregate field answers with.
3634///
3635/// Split from the resolver for the reason [`query_rows`] is: a subscription
3636/// reads them over and over and has to tell one answer from the last.
3637async fn aggregate_value(
3638    ctx: &ResolverContext<'_>,
3639    spec: &AggregateSpec,
3640) -> Result<Value, async_graphql::Error> {
3641    use crate::schema::aggregate as agg;
3642
3643    let pool = ctx.data::<PgPool>()?;
3644    let gql_ctx = ctx.data::<GraphQLContext>()?;
3645
3646    let mut bound_values: Vec<serde_json::Value> = Vec::new();
3647    // A function's own arguments are bound before anything else, so a
3648    // predicate written beside them continues the numbering rather than
3649    // colliding with it.
3650    let source = match &spec.call {
3651        None => format!(
3652            "{}.{}",
3653            postrust_sql::escape_ident(&spec.schema_name),
3654            postrust_sql::escape_ident(&spec.table_name)
3655        ),
3656        Some(call) => function_source(ctx, call, &spec.table_name, &mut bound_values)?,
3657    };
3658
3659    let mut where_sql = String::new();
3660    {
3661        // Counting rows is reading them, so the permission applies here in the
3662        // same breath as the request's own filter -- otherwise `count` would
3663        // report a total the same role cannot list.
3664        let requested = ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v));
3665        let permission = permission_predicate(
3666            &gql_ctx.caller(),
3667            spec.names.as_ref(),
3668            &spec.schema_name,
3669            &spec.table_name,
3670        )?;
3671        if let Some(predicate) = and_predicates(requested, permission) {
3672            let guard = gql_ctx
3673                .schema_cache
3674                .get()
3675                .await
3676                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3677            let cache = guard
3678                .as_ref()
3679                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3680            // A function's rows are read under the table's name as an alias,
3681            // not from the table itself -- the same split the row read makes.
3682            // Counting them under the qualified name answered `invalid
3683            // reference to FROM-clause entry for table "Track"` the moment a
3684            // permission referred to a column, which is any permission that
3685            // follows a relationship.
3686            let scope = match &spec.call {
3687                None => WhereScope::table(
3688                    &spec.schema_name,
3689                    &spec.table_name,
3690                    &spec.type_name,
3691                    spec.names.as_ref(),
3692                )
3693                .with_resolution(cache, spec.relationships.as_ref()),
3694                Some(_) => WhereScope::for_alias(
3695                    &spec.schema_name,
3696                    &spec.table_name,
3697                    &spec.table_name,
3698                    &spec.type_name,
3699                    cache,
3700                    spec.relationships.as_ref(),
3701                    spec.names.as_ref(),
3702                ),
3703            }
3704            .for_caller(gql_ctx.caller());
3705            let (sql, values) =
3706                build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3707            if !sql.is_empty() {
3708                where_sql = format!(" {}", sql);
3709                bound_values.extend(values);
3710            }
3711        }
3712    }
3713
3714    let order_sql = build_order_by_clause(
3715        ctx,
3716        &gql_ctx.schema_cache,
3717        &spec.schema_name,
3718        &spec.table_name,
3719        &spec.type_name,
3720        spec.relationships.as_ref(),
3721        &format!(
3722            "{}.{}",
3723            postrust_sql::escape_ident(&spec.schema_name),
3724            postrust_sql::escape_ident(&spec.table_name)
3725        ),
3726        spec.names.as_ref(),
3727    )
3728    .await?;
3729
3730    let requested_limit = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
3731    let offset = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
3732    let ceiling = match (
3733        spec.max_rows,
3734        permission_limit(
3735            &gql_ctx.caller(),
3736            spec.names.as_ref(),
3737            &spec.schema_name,
3738            &spec.table_name,
3739        ),
3740    ) {
3741        (Some(configured), Some(granted)) => Some(configured.min(granted)),
3742        (only, None) | (None, only) => only,
3743    };
3744    let limit = match (requested_limit, ceiling) {
3745        (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
3746        (Some(requested), None) => Some(requested),
3747        (None, ceiling) => ceiling,
3748    };
3749
3750    let rows = format!("SELECT * FROM {}{}{}", source, where_sql, order_sql);
3751
3752    // What `nodes` reads: the page, bounded by whichever ceiling is lowest.
3753    let mut inner = rows.clone();
3754    if let Some(limit) = limit {
3755        inner.push_str(&format!(" LIMIT {}", limit));
3756    }
3757    if let Some(offset) = offset {
3758        inner.push_str(&format!(" OFFSET {}", offset));
3759    }
3760
3761    // What `aggregate` reads, which is not the same rows. A count is not a
3762    // page: a ceiling exists to bound how many rows travel, and answering
3763    // "how many are there" with "as many as I would have sent you" is not an
3764    // answer to the question. Hasura says so too -- a role limited to one row
3765    // of `article` still counts three of them and still gets `max(id)` over
3766    // all three, which is what `agg_perm` tests.
3767    //
3768    // The request's own `limit` and `offset` do still apply: those were asked
3769    // for, and `article_aggregate(limit: 2)` is a question about two rows. The
3770    // corpus proves this only for the permission's ceiling; the configured one
3771    // is treated the same way because the reason is the same, and because a
3772    // server whose `PGRST_MAX_ROWS` silently became the answer to `count`
3773    // would be wrong in the way that is hardest to notice.
3774    let mut counted = rows;
3775    if let Some(limit) = requested_limit {
3776        counted.push_str(&format!(" LIMIT {}", limit));
3777    }
3778    if let Some(offset) = offset {
3779        counted.push_str(&format!(" OFFSET {}", offset));
3780    }
3781
3782    // What the client actually asked for. Two selections of one aggregate are
3783    // one entry: `sum { id }` beside `totals: sum { views }` reads the same
3784    // object under two names, so the columns are the union of what both named
3785    // and the object is built once.
3786    let mut wants_nodes = false;
3787    let mut wanted: BTreeMap<String, Vec<String>> = BTreeMap::new();
3788    // `count` is different: each occurrence may count something else, so each
3789    // is answered under the name it was asked for.
3790    let mut counts: Vec<(String, String)> = Vec::new();
3791    for selection in ctx.field().selection_set() {
3792        match selection.name() {
3793            "nodes" => wants_nodes = true,
3794            "aggregate" => {
3795                for function in selection.selection_set() {
3796                    if function.name() == "count" {
3797                        counts.push((
3798                            function.alias().unwrap_or("count").to_string(),
3799                            count_expression(
3800                                function,
3801                                (&spec.schema_name, &spec.table_name),
3802                                None,
3803                                spec.names.as_ref(),
3804                            ),
3805                        ));
3806                        continue;
3807                    }
3808                    let columns = wanted.entry(function.name().to_string()).or_default();
3809                    for column in function.selection_set() {
3810                        let name = column.name().to_string();
3811                        if !columns.contains(&name) {
3812                            columns.push(name);
3813                        }
3814                    }
3815                }
3816            }
3817            _ => {}
3818        }
3819    }
3820    wanted.retain(|_, columns| !columns.is_empty());
3821
3822    let mut result = async_graphql::indexmap::IndexMap::new();
3823
3824    if !counts.is_empty() || !wanted.is_empty() {
3825        // `count` is answered whether or not it was asked for: the aggregate
3826        // type's own resolver reads a missing key as zero, and a client that
3827        // asked for nothing else still gets a number rather than a null.
3828        let mut parts = match counts.iter().any(|(key, _)| key == "count") {
3829            true => Vec::new(),
3830            false => vec!["'count', count(*)".to_string()],
3831        };
3832        for (key, expression) in &counts {
3833            parts.push(format!("'{}', {}", key.replace('\'', "''"), expression));
3834        }
3835        for (function, columns) in &wanted {
3836            // The function name comes from the schema, not from the request:
3837            // a selection can only name a field that was generated, so there
3838            // is no path from client text to this identifier.
3839            let sql_function = agg::NUMERIC_AGGREGATES
3840                .iter()
3841                .chain(agg::ORDERED_AGGREGATES.iter())
3842                .find(|(name, _)| name == function)
3843                .map(|(name, _)| *name)
3844                .ok_or_else(|| {
3845                    async_graphql::Error::new(format!("unknown aggregate \"{}\"", function))
3846                })?;
3847            let per_column: Vec<String> = columns
3848                .iter()
3849                .map(|field| {
3850                    let source = spec
3851                        .names
3852                        .column_source(&spec.schema_name, &spec.table_name, field)
3853                        .unwrap_or(field);
3854                    format!(
3855                        "'{}', {}({})",
3856                        field.replace('\'', "''"),
3857                        sql_function,
3858                        postrust_sql::escape_ident(source)
3859                    )
3860                })
3861                .collect();
3862            parts.push(format!(
3863                "'{}', json_build_object({})",
3864                sql_function,
3865                per_column.join(", ")
3866            ));
3867        }
3868
3869        let sql = format!(
3870            "SELECT json_build_object({}) FROM ({}) AS pgrst_agg",
3871            parts.join(", "),
3872            counted
3873        );
3874        let mut conn =
3875            begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3876        let rows = execute_query_on(&mut conn, &sql, &bound_values).await?;
3877        conn.commit().await?;
3878        if let Some(first) = rows.into_iter().next() {
3879            if let Value::Object(map) = json_to_value(first) {
3880                result.insert(async_graphql::Name::new("aggregate"), Value::Object(map));
3881            }
3882        }
3883    }
3884
3885    if wants_nodes {
3886        // `nodes` is the same rows selection the plain root field answers, so
3887        // it gets the same projection: computed fields, which are functions of
3888        // the row rather than part of it, and relationships, which are
3889        // correlated subselects. Reading it as columns alone was why a
3890        // relationship asked for beside a count came back null.
3891        let nodes = ctx
3892            .field()
3893            .selection_set()
3894            .find(|selection| selection.name() == "nodes")
3895            .ok_or_else(|| async_graphql::Error::new("nodes was asked for and then was not"))?;
3896
3897        // The aggregate query has already been sent, so its parameters are
3898        // fixed; anything an embed binds continues that numbering in a vector
3899        // of its own.
3900        let mut node_values = bound_values.clone();
3901        let (projection, row_column_types) = {
3902            let guard = gql_ctx
3903                .schema_cache
3904                .get()
3905                .await
3906                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3907            let cache = guard
3908                .as_ref()
3909                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3910            let qi = postrust_core::api_request::QualifiedIdentifier::new(
3911                &spec.schema_name,
3912                &spec.table_name,
3913            );
3914            let table = cache.get_table(&qi);
3915            let mut param_idx = node_values.len() + 1;
3916            let computed = match table {
3917                Some(table) => computed_projections(
3918                    table,
3919                    nodes.selection_set(),
3920                    "src",
3921                    spec.names.as_ref(),
3922                    cache,
3923                    &mut param_idx,
3924                    &mut node_values,
3925                )?,
3926                None => Vec::new(),
3927            };
3928            let embeds = build_embed_expressions(
3929                &gql_ctx.caller(),
3930                cache,
3931                spec.relationships.as_ref(),
3932                &spec.type_name,
3933                "src",
3934                nodes.selection_set(),
3935                spec.max_rows,
3936                &mut 0,
3937                &mut param_idx,
3938                &mut node_values,
3939                spec.names.as_ref(),
3940            )?;
3941            // A renamed column is renamed here, in the projection over the
3942            // subquery -- not inside it, where `src` has to stay the table's
3943            // own composite for a computed field to be passed the row.
3944            let mut projection = table
3945                .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
3946                .unwrap_or_else(|| "src.*".to_string());
3947            for expression in &computed {
3948                projection.push_str(", ");
3949                projection.push_str(expression);
3950            }
3951            for (field_name, expression) in &embeds {
3952                projection.push_str(", ");
3953                projection.push_str(expression);
3954                projection.push_str(" AS ");
3955                projection.push_str(&postrust_sql::escape_ident(field_name));
3956            }
3957            (
3958                projection,
3959                table
3960                    .map(|table| exposed_column_types(table, spec.names.as_ref()))
3961                    .unwrap_or_default(),
3962            )
3963        };
3964
3965        let sql = format!(
3966            "SELECT {} FROM (SELECT {} FROM ({}) AS src) AS pgrst_nodes",
3967            row_json("pgrst_nodes", &row_column_types),
3968            projection,
3969            inner
3970        );
3971        let mut conn =
3972            begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3973        let rows = execute_query_on(&mut conn, &sql, &node_values).await?;
3974        conn.commit().await?;
3975        result.insert(
3976            async_graphql::Name::new("nodes"),
3977            Value::List(rows.into_iter().map(json_to_value).collect()),
3978        );
3979    }
3980
3981    let _ = &spec.type_name;
3982    Ok(Value::Object(result))
3983}
3984
3985/// Resolve a query field.
3986async fn resolve_query<'a>(
3987    ctx: &ResolverContext<'a>,
3988    spec: &QueryFieldSpec,
3989) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3990    Ok(rows_as_field_value(
3991        query_rows(ctx, spec).await?,
3992        spec.is_by_pk,
3993    ))
3994}
3995
3996/// The rows a query field answers with, as JSON.
3997///
3998/// Split from the field's own resolver because a subscription reads the same
3999/// rows over and over and has to tell one answer from the last: a
4000/// `FieldValue` is neither comparable nor readable, and this is.
4001async fn query_rows(
4002    ctx: &ResolverContext<'_>,
4003    spec: &QueryFieldSpec,
4004) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4005    let schema_name = spec.schema_name.as_str();
4006    let table_name = spec.table_name.as_str();
4007    let type_name = spec.type_name.as_str();
4008    let is_by_pk = spec.is_by_pk;
4009    let pk_columns = spec.pk_columns.as_slice();
4010    let max_rows = spec.max_rows;
4011    let relationships = spec.relationships.as_ref();
4012
4013    let pool = ctx.data::<PgPool>()?;
4014    let gql_ctx = ctx.data::<GraphQLContext>()?;
4015
4016    debug!("Resolving query for table: {}", table_name);
4017
4018    // Extract pagination arguments
4019    let requested_limit: Option<i64> = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
4020
4021    let offset: Option<i64> = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
4022
4023    // A query that names no limit would otherwise select the whole table, so
4024    // the configured ceiling is applied as the limit in that case, and as an
4025    // upper bound when the query asks for more than it. A by-PK query resolves
4026    // to at most one row.
4027    let limit: Option<i64> = if is_by_pk {
4028        Some(1)
4029    } else {
4030        // The permission's ceiling is one more of the same kind, so it folds
4031        // in the same way: whichever of the three is smallest wins, and a
4032        // request naming none takes the lowest that was named.
4033        let ceiling = match (
4034            max_rows,
4035            permission_limit(
4036                &gql_ctx.caller(),
4037                spec.names.as_ref(),
4038                schema_name,
4039                table_name,
4040            ),
4041        ) {
4042            (Some(configured), Some(granted)) => Some(configured.min(granted)),
4043            (only, None) | (None, only) => only,
4044        };
4045        match (requested_limit, ceiling) {
4046            (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
4047            (Some(requested), None) => Some(requested),
4048            (None, ceiling) => ceiling,
4049        }
4050    };
4051
4052    // Build the WHERE clause.
4053    //
4054    // A by-PK query filters on the table's key columns; each value is bound as
4055    // a parameter and cast to the column's type, since GraphQL scalars and
4056    // PostgreSQL types do not line up (a `uuid` key arrives as a String). A
4057    // list query filters on the `filter` argument, which takes the same shape
4058    // as a mutation's `where`.
4059    let mut where_sql = String::new();
4060    let mut bound_values: Vec<serde_json::Value> = Vec::new();
4061
4062    // Where the rows come from. A function's own arguments are bound before
4063    // anything else, because they sit in the FROM clause and every other
4064    // parameter is numbered after them.
4065    let source = match &spec.call {
4066        // Aliased rather than named. A whole-row reference -- what a computed
4067        // field is passed -- can only be written as a bare name, and
4068        // `"public"."author"` is not one: PostgreSQL reads it as a column of a
4069        // table called `public`. An alias no column can share is a name that
4070        // works in both positions.
4071        None => format!(
4072            "{}.{} AS {}",
4073            postrust_sql::escape_ident(schema_name),
4074            postrust_sql::escape_ident(table_name),
4075            postrust_sql::escape_ident(READ_ROW)
4076        ),
4077        Some(call) => function_source(ctx, call, table_name, &mut bound_values)?,
4078    };
4079
4080    // How a column of the source is referred to: a table by its qualified
4081    // name, a function's rows by the alias above.
4082    let source_ref = match &spec.call {
4083        None => postrust_sql::escape_ident(READ_ROW),
4084        Some(_) => postrust_sql::escape_ident(table_name),
4085    };
4086
4087    if is_by_pk {
4088        if pk_columns.is_empty() {
4089            return Err(async_graphql::Error::new(format!(
4090                "\"{}\" has no primary key, so it cannot be queried by key",
4091                table_name
4092            )));
4093        }
4094
4095        let mut conditions = Vec::with_capacity(pk_columns.len());
4096        for (idx, (col_name, pg_type)) in pk_columns.iter().enumerate() {
4097            let field = spec
4098                .names
4099                .column(schema_name, table_name, col_name)
4100                .unwrap_or(col_name);
4101            let value = ctx.args.try_get(field).map_err(|_| {
4102                async_graphql::Error::new(format!(
4103                    "missing required primary key argument \"{}\"",
4104                    field
4105                ))
4106            })?;
4107
4108            conditions.push(format!(
4109                "{} = ${}::{}",
4110                postrust_sql::escape_ident(col_name),
4111                idx + 1,
4112                pg_type
4113            ));
4114            bound_values.push(accessor_to_json(&value));
4115        }
4116        where_sql = format!(" WHERE {}", conditions.join(" AND "));
4117    }
4118
4119    // What this role may read, beside what the request asked for. Applied to
4120    // both shapes and to neither: a query naming no `where` at all is still a
4121    // read, and a by-key query that skipped the permission would be the one
4122    // way to fetch a row the filter withholds.
4123    {
4124        let requested = match is_by_pk {
4125            true => None,
4126            false => ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v)),
4127        };
4128        let permission = permission_predicate(
4129            &gql_ctx.caller(),
4130            spec.names.as_ref(),
4131            schema_name,
4132            table_name,
4133        )?;
4134
4135        if let Some(predicate) = and_predicates(requested, permission) {
4136            let guard = gql_ctx
4137                .schema_cache
4138                .get()
4139                .await
4140                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4141            let cache = guard
4142                .as_ref()
4143                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4144            let scope = match &spec.call {
4145                None => WhereScope::table(schema_name, table_name, type_name, spec.names.as_ref())
4146                    .under_alias(READ_ROW)
4147                    .with_resolution(cache, relationships)
4148                    .for_caller(gql_ctx.caller()),
4149                // A function's rows are the table's rows, so they are renamed
4150                // the same way; only how they are reached differs.
4151                Some(_) => WhereScope::for_alias(
4152                    schema_name,
4153                    table_name,
4154                    table_name,
4155                    type_name,
4156                    cache,
4157                    relationships,
4158                    spec.names.as_ref(),
4159                )
4160                .for_caller(gql_ctx.caller()),
4161            };
4162            let (filter_sql, filter_values) =
4163                build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
4164            if !filter_sql.is_empty() {
4165                // A by-key query already has a `WHERE`, so this joins it
4166                // rather than replacing it.
4167                where_sql = match where_sql.is_empty() {
4168                    true => format!(" {}", filter_sql),
4169                    false => format!(
4170                        "{} AND ({})",
4171                        where_sql,
4172                        filter_sql.trim_start_matches("WHERE ")
4173                    ),
4174                };
4175                bound_values.extend(filter_values);
4176            }
4177        }
4178    }
4179
4180    // A by-key query resolves to one row, so neither ordering nor distinct
4181    // has anything to do.
4182    let (order_sql, distinct_on) = if is_by_pk {
4183        (String::new(), Vec::new())
4184    } else {
4185        (
4186            build_order_by_clause(
4187                ctx,
4188                &gql_ctx.schema_cache,
4189                schema_name,
4190                table_name,
4191                type_name,
4192                relationships,
4193                &source_ref,
4194                spec.names.as_ref(),
4195            )
4196            .await?,
4197            build_distinct_on(
4198                ctx,
4199                &gql_ctx.schema_cache,
4200                schema_name,
4201                table_name,
4202                spec.names.as_ref(),
4203            )
4204            .await?,
4205        )
4206    };
4207
4208    // PostgreSQL keeps the first row of each DISTINCT ON group in the query's
4209    // own order, and picks arbitrarily where the ordering does not begin with
4210    // the distinct columns -- so which row survives would depend on the plan.
4211    // Hasura refuses that query rather than answering it, and so does this:
4212    // prepending the distinct columns instead produced an answer, and a wrong
4213    // one, since `ORDER BY "department", "department" DESC` is decided by its
4214    // first term and sorts ascending.
4215    let (distinct_sql, order_sql) = {
4216        let written = order_sql.strip_prefix(" ORDER BY ");
4217        // Reported against the field's arguments as a whole: the rule is about
4218        // two of them disagreeing, so neither one is where it went wrong.
4219        let (prefix, order) = distinct_on_clause(&distinct_on, written).map_err(|error| {
4220            at_path(
4221                error,
4222                &format!(
4223                    "$.selectionSet.{}.args",
4224                    ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4225                ),
4226            )
4227        })?;
4228        (
4229            prefix,
4230            match order {
4231                Some(order) => format!(" ORDER BY {}", order),
4232                None => String::new(),
4233            },
4234        )
4235    };
4236
4237    // A computed column is a function of the row rather than part of it, so
4238    // it is not in `*` and is named only when it was asked for.
4239    let (computed, row_column_types) = {
4240        let guard = gql_ctx
4241            .schema_cache
4242            .get()
4243            .await
4244            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4245        match guard.as_ref() {
4246            Some(cache) => {
4247                let qi =
4248                    postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
4249                match cache.get_table(&qi) {
4250                    Some(table) => {
4251                        let mut param_idx = bound_values.len() + 1;
4252                        (
4253                            computed_projections(
4254                                table,
4255                                ctx.field().selection_set(),
4256                                "src",
4257                                spec.names.as_ref(),
4258                                cache,
4259                                &mut param_idx,
4260                                &mut bound_values,
4261                            )?,
4262                            exposed_column_types(table, spec.names.as_ref()),
4263                        )
4264                    }
4265                    None => (Vec::new(), HashMap::new()),
4266                }
4267            }
4268            None => (Vec::new(), HashMap::new()),
4269        }
4270    };
4271    // ORDER BY, LIMIT and OFFSET belong inside the subquery: applying them to
4272    // the outer `row_to_json` projection would leave the ordering of the rows
4273    // that survive the limit unspecified.
4274    let mut inner = format!(
4275        "SELECT {}* FROM {}{}{}",
4276        distinct_sql, source, where_sql, order_sql
4277    );
4278
4279    if let Some(limit) = limit {
4280        inner.push_str(&format!(" LIMIT {}", limit));
4281    }
4282
4283    if let Some(offset) = offset {
4284        inner.push_str(&format!(" OFFSET {}", offset));
4285    }
4286
4287    // Embed the requested relationships in this same query, as correlated
4288    // subselects in the SELECT list, so the whole selection is one round trip.
4289    let embed_expressions = {
4290        let guard = gql_ctx
4291            .schema_cache
4292            .get()
4293            .await
4294            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4295        match guard.as_ref() {
4296            Some(cache) => {
4297                // Parameters bound inside an embed continue the outer query's
4298                // numbering: sqlx binds by position, so the values only have
4299                // to be pushed in the order their placeholders were handed
4300                // out.
4301                let mut param_idx = bound_values.len() + 1;
4302                build_embed_expressions(
4303                    &gql_ctx.caller(),
4304                    cache,
4305                    relationships,
4306                    type_name,
4307                    "src",
4308                    ctx.field().selection_set(),
4309                    max_rows,
4310                    &mut 0,
4311                    &mut param_idx,
4312                    &mut bound_values,
4313                    spec.names.as_ref(),
4314                )?
4315            }
4316            None => Vec::new(),
4317        }
4318    };
4319
4320    // A computed column is a function of the row, and the row it is a function
4321    // of is the table's -- not the subquery's. Projecting it inside the
4322    // subquery gave `src` an extra column, which is what made a computed
4323    // relationship beside it fail with `cannot cast type record to author`: a
4324    // subquery alias is only passable as a composite value while its columns
4325    // are exactly the table's. So both live out here, where `src` still is
4326    // one.
4327    let renamed = {
4328        let guard = gql_ctx
4329            .schema_cache
4330            .get()
4331            .await
4332            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4333        guard
4334            .as_ref()
4335            .and_then(|cache| {
4336                cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
4337                    schema_name,
4338                    table_name,
4339                ))
4340            })
4341            .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4342    };
4343    let inner = if embed_expressions.is_empty() && computed.is_empty() && renamed.is_none() {
4344        inner
4345    } else {
4346        let mut projection = renamed.unwrap_or_else(|| "src.*".to_string());
4347        for expression in &computed {
4348            projection.push_str(", ");
4349            projection.push_str(expression);
4350        }
4351        for (field_name, expression) in &embed_expressions {
4352            projection.push_str(", ");
4353            projection.push_str(expression);
4354            projection.push_str(" AS ");
4355            projection.push_str(&postrust_sql::escape_ident(field_name));
4356        }
4357        format!("SELECT {} FROM ({}) AS src", projection, inner)
4358    };
4359
4360    let sql = format!(
4361        "SELECT {} FROM ({}) t",
4362        row_json("t", &row_column_types),
4363        inner
4364    );
4365
4366    // One transaction for the query and any embeds hanging off it, so the role
4367    // applies to all of them and the parent and child rows come from a single
4368    // snapshot.
4369    let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
4370
4371    // Execute query - returns Vec<serde_json::Value>
4372    let mut result = execute_query_on(&mut tx, &sql, &bound_values).await?;
4373
4374    // Anything the single-query form did not cover.
4375    if embed_expressions.is_empty() {
4376        let guard = gql_ctx
4377            .schema_cache
4378            .get()
4379            .await
4380            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4381        let cache = guard
4382            .as_ref()
4383            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4384
4385        let embed_ctx = EmbedContext {
4386            schema_cache: cache,
4387            relationships,
4388            max_rows,
4389        };
4390
4391        embed_relationships(&mut tx, &embed_ctx, type_name, ctx.field(), &mut result).await?;
4392    }
4393
4394    tx.commit().await?;
4395
4396    Ok(result)
4397}
4398
4399/// The rows a field answers with, in the shape its type says.
4400///
4401/// A by-key field is one row or none; every other is a list, empty where
4402/// nothing matched rather than null.
4403fn rows_as_field_value<'a>(rows: Vec<serde_json::Value>, is_by_pk: bool) -> Option<FieldValue<'a>> {
4404    match is_by_pk {
4405        true => rows
4406            .into_iter()
4407            .next()
4408            .map(|v| FieldValue::value(json_to_value(v))),
4409        false => Some(FieldValue::list(
4410            rows.into_iter()
4411                .map(|v| FieldValue::value(json_to_value(v))),
4412        )),
4413    }
4414}
4415
4416/// Resolve a mutation field.
4417#[allow(clippy::too_many_arguments)]
4418async fn resolve_mutation<'a>(
4419    ctx: &ResolverContext<'a>,
4420    schema_name: &str,
4421    table_name: &str,
4422    mutation_type: MutationType,
4423    pk_columns: &[(String, String)],
4424    relationships: &Arc<HashMap<String, Vec<RelationshipField>>>,
4425    type_names: &Arc<HashMap<(String, String), String>>,
4426    names: &Arc<crate::names::NameOverrides>,
4427    max_rows: Option<i64>,
4428) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4429    let pool = ctx.data::<PgPool>()?;
4430    let gql_ctx = ctx.data::<GraphQLContext>()?;
4431
4432    debug!(
4433        "Resolving mutation for table: {} type: {:?}",
4434        table_name, mutation_type
4435    );
4436
4437    // A relationship or a computed field asked for beside the written columns
4438    // is not in `RETURNING`, so the rows are read again through the projection
4439    // an ordinary query uses -- and only when the selection asks for something
4440    // that needs it. A delete cannot do that afterwards and builds it into the
4441    // statement instead, which is why this is settled before the statement is.
4442    let type_name = type_names
4443        .get(&(schema_name.to_string(), table_name.to_string()))
4444        .cloned()
4445        .unwrap_or_else(|| table_name.to_string());
4446
4447    let by_key = matches!(
4448        mutation_type,
4449        MutationType::InsertOne | MutationType::UpdateByPk | MutationType::DeleteByPk
4450    );
4451    let returning = if by_key {
4452        Some(ctx.field())
4453    } else {
4454        ctx.field()
4455            .selection_set()
4456            .find(|selection| selection.name() == "returning")
4457    };
4458
4459    // Several updates, each with its own filter, applied in the order given.
4460    // They share the operation's transaction like any other write, which is
4461    // the whole difference from sending them one at a time: either all of them
4462    // happened or none did.
4463    if mutation_type == MutationType::UpdateMany {
4464        let updates = ctx
4465            .args
4466            .try_get("updates")
4467            .ok()
4468            .map(|v| accessor_to_json(&v))
4469            .unwrap_or(serde_json::Value::Null);
4470        let serde_json::Value::Array(entries) = updates else {
4471            return Err(async_graphql::Error::new(
4472                "\"updates\" takes a list of {where, _set, …} objects",
4473            ));
4474        };
4475        let column_types = column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await;
4476        let mut answers: Vec<FieldValue<'_>> = Vec::with_capacity(entries.len());
4477        for entry in entries {
4478            let serde_json::Value::Object(entry) = entry else {
4479                return Err(async_graphql::Error::new(
4480                    "each update is an object of {where, _set, …}",
4481                ));
4482            };
4483            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4484                .iter()
4485                .filter_map(|name| entry.get(*name).map(|value| (*name, value.clone())))
4486                .filter(|(_, value)| !value.is_null())
4487                .collect();
4488            let rows = execute_update(
4489                pool,
4490                gql_ctx,
4491                schema_name,
4492                table_name,
4493                &type_name,
4494                operators,
4495                column_types.clone(),
4496                entry.get("where").cloned(),
4497                relationships.as_ref(),
4498                names.as_ref(),
4499            )
4500            .await?;
4501            let affected = rows.len();
4502            let rows = match returning {
4503                Some(returning) if !rows.is_empty() => {
4504                    reread_returning(
4505                        pool,
4506                        gql_ctx,
4507                        schema_name,
4508                        table_name,
4509                        &type_name,
4510                        rows,
4511                        returning,
4512                        relationships.as_ref(),
4513                        names.as_ref(),
4514                        max_rows,
4515                    )
4516                    .await?
4517                }
4518                _ => rows,
4519            };
4520            answers.extend(mutation_result(rows, affected, false));
4521        }
4522        return Ok(Some(FieldValue::list(answers)));
4523    }
4524
4525    let (result, affected) = match mutation_type {
4526        MutationType::Insert | MutationType::InsertOne => {
4527            let objects = ctx
4528                .args
4529                .try_get("objects")
4530                .ok()
4531                .map(|v| accessor_to_json(&v))
4532                .or_else(|| {
4533                    // `insert_x_one(object: {...})` is one row spelled without
4534                    // the list.
4535                    ctx.args
4536                        .try_get("object")
4537                        .ok()
4538                        .map(|v| serde_json::Value::Array(vec![accessor_to_json(&v)]))
4539                })
4540                .unwrap_or_else(|| serde_json::Value::Array(vec![]));
4541
4542            let guard = gql_ctx
4543                .schema_cache
4544                .get()
4545                .await
4546                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4547            let cache = guard
4548                .as_ref()
4549                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4550            // Where this write is, in the document the client sent. The
4551            // field as it was written -- an alias if there was one, since that
4552            // is what names this selection.
4553            let here = format!(
4554                "$.selectionSet.{}",
4555                ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4556            );
4557            // `insert_x(objects: [...])` against `insert_x_one(object: {...})`:
4558            // the same write under two spellings, and the path says which.
4559            let named = match mutation_type {
4560                MutationType::InsertOne => "object",
4561                _ => "objects",
4562            };
4563            let context = InsertContext {
4564                on_conflict: ctx
4565                    .args
4566                    .try_get("on_conflict")
4567                    .ok()
4568                    .map(|v| accessor_to_json(&v))
4569                    .filter(|v| !v.is_null()),
4570                cache,
4571                relationships: relationships.as_ref(),
4572                type_names: type_names.as_ref(),
4573                names: names.as_ref(),
4574                caller: gql_ctx.caller(),
4575                // Filled in per row by `execute_insert`, which is where the
4576                // index is known.
4577                row: String::new(),
4578                objects: format!("{}.args.{}", here, named),
4579                conflict: format!("{}.args.on_conflict", here),
4580            };
4581
4582            execute_insert(
4583                pool,
4584                gql_ctx,
4585                schema_name,
4586                table_name,
4587                objects,
4588                &context,
4589                mutation_type == MutationType::InsertOne,
4590            )
4591            .await?
4592        }
4593        // `UpdateMany` answered above: it is a list of responses rather than
4594        // one, so it does not share this shape.
4595        MutationType::UpdateMany | MutationType::Update | MutationType::UpdateByPk => {
4596            // `_set` replaces, the others read the column they write. A client
4597            // may send more than one, so all of them are collected rather than
4598            // the first that happens to be present.
4599            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4600                .iter()
4601                .filter_map(|name| {
4602                    ctx.args
4603                        .try_get(name)
4604                        .ok()
4605                        .map(|v| (*name, accessor_to_json(&v)))
4606                })
4607                .filter(|(_, value)| !value.is_null())
4608                .collect();
4609
4610            let where_clause = if mutation_type == MutationType::UpdateByPk {
4611                Some(pk_where_from_args(
4612                    ctx,
4613                    schema_name,
4614                    table_name,
4615                    pk_columns,
4616                    names.as_ref(),
4617                )?)
4618            } else {
4619                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4620            };
4621            // Which rows this role may change, beside which rows the request
4622            // asked to change. A by-key update is narrowed too: naming the key
4623            // of a row the permission withholds must not be a way to reach it.
4624            let where_clause = and_predicates(
4625                where_clause,
4626                permission_filter(
4627                    &gql_ctx.caller(),
4628                    names.as_ref(),
4629                    schema_name,
4630                    table_name,
4631                    crate::role::Verb::Update,
4632                )?,
4633            );
4634
4635            execute_update(
4636                pool,
4637                gql_ctx,
4638                schema_name,
4639                table_name,
4640                &type_name,
4641                operators,
4642                column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await,
4643                where_clause,
4644                relationships.as_ref(),
4645                names.as_ref(),
4646            )
4647            .await
4648            .map(|rows| {
4649                let count = rows.len();
4650                (rows, count)
4651            })?
4652        }
4653        MutationType::Delete | MutationType::DeleteByPk => {
4654            let where_clause = if mutation_type == MutationType::DeleteByPk {
4655                Some(pk_where_from_args(
4656                    ctx,
4657                    schema_name,
4658                    table_name,
4659                    pk_columns,
4660                    names.as_ref(),
4661                )?)
4662            } else {
4663                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4664            };
4665            let where_clause = and_predicates(
4666                where_clause,
4667                permission_filter(
4668                    &gql_ctx.caller(),
4669                    names.as_ref(),
4670                    schema_name,
4671                    table_name,
4672                    crate::role::Verb::Delete,
4673                )?,
4674            );
4675
4676            execute_delete(
4677                pool,
4678                gql_ctx,
4679                schema_name,
4680                table_name,
4681                &type_name,
4682                where_clause,
4683                returning,
4684                relationships.as_ref(),
4685                names.as_ref(),
4686                max_rows,
4687            )
4688            .await
4689            .map(|rows| {
4690                let count = rows.len();
4691                (rows, count)
4692            })?
4693        }
4694    };
4695
4696    let is_delete = matches!(
4697        mutation_type,
4698        MutationType::Delete | MutationType::DeleteByPk
4699    );
4700    let result = match returning {
4701        Some(returning) if !result.is_empty() && !is_delete => {
4702            reread_returning(
4703                pool,
4704                gql_ctx,
4705                schema_name,
4706                table_name,
4707                &type_name,
4708                result,
4709                returning,
4710                relationships.as_ref(),
4711                names.as_ref(),
4712                max_rows,
4713            )
4714            .await?
4715        }
4716        _ => result,
4717    };
4718
4719    Ok(mutation_result(result, affected, by_key))
4720}
4721
4722/// The transaction this operation's writes share, opening it if this is the
4723/// first one.
4724///
4725/// Held for the length of the write, and settled by whoever answers the
4726/// request -- see [`crate::context::SharedWrite`]. Mutation root fields are
4727/// resolved one after another, so the lock is never contended; it is what
4728/// makes the transaction reachable from a resolver that owns nothing.
4729async fn write_tx<'a>(
4730    gql_ctx: &'a GraphQLContext,
4731    pool: &PgPool,
4732) -> Result<
4733    tokio::sync::MutexGuard<'a, Option<sqlx::Transaction<'static, sqlx::Postgres>>>,
4734    async_graphql::Error,
4735> {
4736    let mut guard = gql_ctx.write.lock().await;
4737    if guard.is_none() {
4738        *guard = Some(begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?);
4739    }
4740    Ok(guard)
4741}
4742
4743/// Begin a transaction with the request's role and session variables applied.
4744///
4745/// The settings go in before the role does. A role with fewer privileges may
4746/// not be allowed to call `set_config` at all, and a policy that reads a
4747/// setting the caller could then change is not a policy.
4748async fn begin_with_session(
4749    pool: &PgPool,
4750    role: &str,
4751    settings: &[(String, String)],
4752) -> Result<sqlx::Transaction<'static, sqlx::Postgres>, async_graphql::Error> {
4753    let mut tx = pool.begin().await?;
4754
4755    // One statement for all of them, which is what the REST path does. A loop
4756    // here costs a round trip per session variable, and there is always at
4757    // least one: `hasura.session` and `hasura.role` are set on every request,
4758    // so even an anonymous read paid two. An authenticated caller with six
4759    // `x-hasura-*` variables paid eight.
4760    //
4761    // The values stay bound rather than interpolated. `set_config` takes both
4762    // as arguments, where `SET LOCAL` would need the value written into the
4763    // statement text.
4764    if !settings.is_empty() {
4765        let calls = (0..settings.len())
4766            .map(|i| format!("set_config(${}, ${}, true)", i * 2 + 1, i * 2 + 2))
4767            .collect::<Vec<_>>()
4768            .join(", ");
4769        let sql = format!("SELECT {calls}");
4770        let mut query = sqlx::query_scalar::<_, String>(&sql);
4771        for (name, value) in settings {
4772            query = query.bind(name).bind(value);
4773        }
4774        query.fetch_optional(&mut *tx).await?;
4775    }
4776
4777    sqlx::query(&format!(
4778        "SET LOCAL ROLE {}",
4779        postrust_sql::escape_ident(role)
4780    ))
4781    .execute(&mut *tx)
4782    .await?;
4783    Ok(tx)
4784}
4785
4786/// Render a row as JSON, with any shape column rendered as GeoJSON.
4787///
4788/// PostgreSQL's text form for a geometry is WKB hex --
4789/// `0103000020E6100000…` -- which is what `row_to_json` puts in the response
4790/// and is not what any client can read. Hasura answers GeoJSON, and a client
4791/// migrating from it parses GeoJSON.
4792///
4793/// The shape columns are merged over the row rather than replacing the
4794/// projection, so a table with no shape column produces exactly the SQL it did
4795/// before and one with three needs no list of the others.
4796fn row_json(expr: &str, column_types: &HashMap<String, String>) -> String {
4797    let mut shapes: Vec<(&String, &String)> = column_types
4798        .iter()
4799        .filter(|(_, pg_type)| matches!(pg_type.as_str(), "geometry" | "geography"))
4800        .collect();
4801    if shapes.is_empty() {
4802        return format!("row_to_json({})", expr);
4803    }
4804    shapes.sort();
4805
4806    let overrides: Vec<String> = shapes
4807        .iter()
4808        .map(|(name, _)| {
4809            format!(
4810                // Option 4 asks PostGIS for the long-form CRS member --
4811                // `urn:ogc:def:crs:EPSG::4326` -- which Hasura includes and
4812                // which a client round-tripping a shape needs in order to
4813                // write it back where it came from. PostGIS emits it only
4814                // where the geometry has an SRID to report.
4815                "'{}', ST_AsGeoJSON({}.{}, 9, 4)::jsonb",
4816                name.replace('\'', "''"),
4817                expr,
4818                postrust_sql::escape_ident(name)
4819            )
4820        })
4821        .collect();
4822    format!(
4823        "(to_jsonb({}) || jsonb_build_object({}))",
4824        expr,
4825        overrides.join(", ")
4826    )
4827}
4828
4829/// Execute a SQL query and return results as serde_json::Value.
4830/// We keep data as serde_json::Value so field resolvers can use try_downcast_ref.
4831async fn execute_query_on(
4832    conn: &mut sqlx::PgConnection,
4833    sql: &str,
4834    params: &[serde_json::Value],
4835) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4836    use sqlx::Row;
4837
4838    trace!("Executing SQL: {}", sql);
4839
4840    // Execute query
4841    let mut query = sqlx::query(sql);
4842    for param in params {
4843        query = bind_json_value(query, param);
4844    }
4845    let rows = query.fetch_all(&mut *conn).await.map_err(database_error)?;
4846
4847    // Return raw JSON values - don't convert to async_graphql::Value
4848    // This allows field resolvers to use try_downcast_ref::<serde_json::Value>()
4849    let results: Vec<serde_json::Value> = rows
4850        .into_iter()
4851        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
4852        .collect();
4853
4854    Ok(results)
4855}
4856
4857/// Refuse a GeoJSON document PostGIS would accept and should not.
4858///
4859/// `ST_GeomFromGeoJSON` will build a "Polygon" from three points that do not
4860/// meet, and a "LineString" from one point -- neither of which is a shape, and
4861/// both of which are then stored and read back as though they were. Hasura
4862/// checks the document before it reaches the database, refuses with
4863/// `parse-failed`, and says which rule was broken; the messages here are its
4864/// messages, because a client that reports them to a user is reporting text it
4865/// already ships.
4866///
4867/// A value that is not an object is left alone: a string in a geometry column
4868/// is WKT or WKB hex, which is a different thing entirely.
4869fn check_geojson(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4870    fn refuse<T>(message: &str) -> Result<T, async_graphql::Error> {
4871        Err(coded_error("parse-failed", message))
4872    }
4873
4874    fn position(
4875        value: &serde_json::Value,
4876    ) -> Result<&Vec<serde_json::Value>, async_graphql::Error> {
4877        match value.as_array() {
4878            Some(items) if items.len() >= 2 && items.iter().all(|i| i.is_number()) => Ok(items),
4879            _ => refuse("A Position needs at least 2 elements"),
4880        }
4881    }
4882
4883    fn positions(
4884        value: Option<&serde_json::Value>,
4885        least: usize,
4886        what: &str,
4887    ) -> Result<Vec<Vec<serde_json::Value>>, async_graphql::Error> {
4888        let items = match value.and_then(|v| v.as_array()) {
4889            Some(items) => items,
4890            None => return refuse(&format!("A {} needs at least {} Positions", what, least)),
4891        };
4892        if items.len() < least {
4893            return refuse(&format!("A {} needs at least {} Positions", what, least));
4894        }
4895        items.iter().map(|item| position(item).cloned()).collect()
4896    }
4897
4898    fn ring(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4899        let points = positions(Some(value), 4, "LinearRing")?;
4900        match (points.first(), points.last()) {
4901            (Some(first), Some(last)) if first == last => Ok(()),
4902            _ => refuse("the first and last locations have to be equal for a LinearRing"),
4903        }
4904    }
4905
4906    fn polygon(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4907        let Some(rings) = value.as_array() else {
4908            return refuse("A LinearRing needs at least 4 Positions");
4909        };
4910        rings.iter().try_for_each(ring)
4911    }
4912
4913    let serde_json::Value::Object(map) = value else {
4914        return Ok(());
4915    };
4916    let Some(kind) = map.get("type").and_then(|v| v.as_str()) else {
4917        return Ok(());
4918    };
4919    let coordinates = map.get("coordinates");
4920    match kind {
4921        "Point" => {
4922            position(coordinates.unwrap_or(&serde_json::Value::Null))?;
4923        }
4924        "MultiPoint" => {
4925            positions(coordinates, 0, "MultiPoint")?;
4926        }
4927        "LineString" => {
4928            positions(coordinates, 2, "LineString")?;
4929        }
4930        "MultiLineString" => {
4931            for line in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4932                positions(Some(line), 2, "LineString")?;
4933            }
4934        }
4935        "Polygon" => polygon(coordinates.unwrap_or(&serde_json::Value::Null))?,
4936        "MultiPolygon" => {
4937            for one in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4938                polygon(one)?;
4939            }
4940        }
4941        "GeometryCollection" => {
4942            for geometry in map
4943                .get("geometries")
4944                .and_then(|v| v.as_array())
4945                .into_iter()
4946                .flatten()
4947            {
4948                check_geojson(geometry)?;
4949            }
4950        }
4951        other => return refuse(&format!("unexpected geometry type: {}", other)),
4952    }
4953    Ok(())
4954}
4955
4956/// The call that produces a computed field's value.
4957///
4958/// Two spellings of the same row, because the two calls need different ones.
4959/// A positional call takes `alias.*`, which is what a qualified table can be
4960/// written as; a named call takes the row as one value, which only a bare
4961/// alias is. Named notation is needed exactly where a session argument is,
4962/// since the row is then not the only parameter and may not be the first.
4963fn computed_call(
4964    definition: &postrust_core::schema_cache::ComputedColumn,
4965    row: &str,
4966    row_by_name: &str,
4967) -> String {
4968    let function = format!(
4969        "{}.{}",
4970        postrust_sql::escape_ident(&definition.function.schema),
4971        postrust_sql::escape_ident(&definition.function.name)
4972    );
4973    match (&definition.session_argument, &definition.row_argument) {
4974        (Some(session), Some(row_argument)) => format!(
4975            "{}({} => {}, {} => {})",
4976            function,
4977            postrust_sql::escape_ident(row_argument),
4978            row_by_name,
4979            postrust_sql::escape_ident(session),
4980            SESSION_ARGUMENT
4981        ),
4982        _ => format!("{}({})", function, row),
4983    }
4984}
4985
4986/// What a `hasura_session` argument is given.
4987///
4988/// The session document, read from the setting `begin_with_session` writes.
4989/// `current_setting`'s second argument makes a missing setting null rather than
4990/// an error, which is the case of a request with no session at all.
4991const SESSION_ARGUMENT: &str = "coalesce(current_setting('hasura.session', true), '{}')::json";
4992
4993/// Everything an update may be told to do, in the order the schema declares
4994/// them. Read by both spellings: one update, and one of many.
4995const UPDATE_OPERATORS: [&str; 7] = [
4996    "_set",
4997    "_inc",
4998    "_append",
4999    "_prepend",
5000    "_delete_key",
5001    "_delete_elem",
5002    "_delete_at_path",
5003];
5004
5005/// What a read table is called inside its own query.
5006///
5007/// See [`WRITTEN_ROW`]: the row a query passes to a computed field has to be
5008/// named, and a table's qualified name is not a name a row can be written
5009/// under.
5010const READ_ROW: &str = "pgrst_src";
5011
5012/// What a written table is called inside its own statement.
5013///
5014/// See [`WhereScope::under_alias`]: the row a statement returns has to be
5015/// named, and a table's own name is read as a column first.
5016const WRITTEN_ROW: &str = "pgrst_row";
5017
5018/// The cast a written value needs to reach a column of this type.
5019///
5020/// A bound parameter arrives as text. PostgreSQL will coerce it to a numeric
5021/// or a date on assignment, but not to `jsonb`, an array, or a user-defined
5022/// type -- `column "details" is of type jsonb but expression is of type text`
5023/// is what an uncast insert answers. Naming the column's own type covers all
5024/// of them and changes nothing where the coercion would have worked anyway.
5025fn write_cast(column_types: &HashMap<String, String>, column: &str) -> String {
5026    match column_types.get(column) {
5027        Some(pg_type) => format!("::{}", pg_type),
5028        None => String::new(),
5029    }
5030}
5031
5032/// The value one column is written with, as it goes over the wire.
5033///
5034/// A `json` column holds a JSON value, and `"[]"` is one -- a string whose
5035/// characters happen to be brackets. Binding the bare text and casting stored
5036/// an empty array instead, so a client that wrote a string read back a list.
5037/// The document a column is given is the document it keeps.
5038fn write_operand(
5039    column_types: &HashMap<String, String>,
5040    column: &str,
5041    value: &serde_json::Value,
5042) -> serde_json::Value {
5043    match column_types.get(column).map(String::as_str) {
5044        Some("json") | Some("jsonb") => match value {
5045            serde_json::Value::String(_)
5046            | serde_json::Value::Number(_)
5047            | serde_json::Value::Bool(_) => serde_json::Value::String(value.to_string()),
5048            other => other.clone(),
5049        },
5050        // An array column takes an array literal. `["a","b"]` is JSON, and
5051        // PostgreSQL reads a text parameter destined for a `text[]` as
5052        // `{a,b}` -- `malformed array literal` is what it says about the
5053        // other spelling.
5054        Some(pg_type) if is_array_type(pg_type) => match value {
5055            serde_json::Value::Array(_) => serde_json::Value::String(array_literal(value)),
5056            other => other.clone(),
5057        },
5058        _ => value.clone(),
5059    }
5060}
5061
5062/// Whether a PostgreSQL type name is an array.
5063///
5064/// Both spellings appear in the catalogue depending on how it was asked:
5065/// `text[]` from `format_type`, `_text` from `typname`.
5066fn is_array_type(pg_type: &str) -> bool {
5067    pg_type.ends_with("[]") || pg_type.starts_with('_')
5068}
5069
5070/// A JSON array as PostgreSQL writes one.
5071///
5072/// `["a","b"]` becomes `{"a","b"}`. Every element is quoted, so a value
5073/// containing a comma, a brace or a backslash survives; a null is the unquoted
5074/// `NULL`, which is the only way to write one.
5075fn array_literal(value: &serde_json::Value) -> String {
5076    fn element(value: &serde_json::Value) -> String {
5077        match value {
5078            serde_json::Value::Null => "NULL".to_string(),
5079            serde_json::Value::Array(_) => array_literal(value),
5080            serde_json::Value::String(text) => {
5081                format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
5082            }
5083            other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
5084        }
5085    }
5086    let items = match value {
5087        serde_json::Value::Array(items) => items,
5088        other => return element(other),
5089    };
5090    format!(
5091        "{{{}}}",
5092        items.iter().map(element).collect::<Vec<_>>().join(",")
5093    )
5094}
5095
5096/// The expression that writes one bound value into one column.
5097///
5098/// A cast is enough for almost everything. A shape is the exception: a client
5099/// sends GeoJSON, which is what Hasura accepts, and `'{"type":"Point",…}'` is
5100/// not something PostgreSQL will cast to a geometry -- it has a function for
5101/// reading that, and the function is the only way in.
5102///
5103/// Only when the value actually is an object. A string in a geometry column is
5104/// WKT or WKB hex, which the cast does read, and passing that to
5105/// `ST_GeomFromGeoJSON` would refuse a perfectly good value.
5106fn write_expression(
5107    column_types: &HashMap<String, String>,
5108    column: &str,
5109    value: &serde_json::Value,
5110    placeholder: &str,
5111) -> String {
5112    let pg_type = column_types.get(column).map(String::as_str);
5113    if value.is_object() && matches!(pg_type, Some("geometry") | Some("geography")) {
5114        return match pg_type {
5115            Some("geography") => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
5116            _ => format!("ST_GeomFromGeoJSON({})", placeholder),
5117        };
5118    }
5119    // An amount written as a number is reached through one: PostgreSQL has no
5120    // cast from `double precision` to `money`, which is what a bound JSON
5121    // number arrives as, and `numeric` is the type it does take one from. One
5122    // written as text -- `"$12,344.57"` -- is read by `money` directly, and
5123    // going through `numeric` would refuse the currency symbol.
5124    if pg_type == Some("money") && value.is_number() {
5125        return format!("{}::numeric::money", placeholder);
5126    }
5127    format!("{}{}", placeholder, write_cast(column_types, column))
5128}
5129
5130/// Every column of a table with the type it is declared as.
5131async fn column_types_of(
5132    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
5133    schema_name: &str,
5134    table_name: &str,
5135) -> HashMap<String, String> {
5136    let Ok(guard) = schema_cache.get().await else {
5137        return HashMap::new();
5138    };
5139    let Some(cache) = guard.as_ref() else {
5140        return HashMap::new();
5141    };
5142    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5143    match cache.get_table(&qi) {
5144        Some(table) => table
5145            .columns
5146            .values()
5147            .map(|c| (c.name.clone(), c.nominal_type.clone()))
5148            .collect(),
5149        None => HashMap::new(),
5150    }
5151}
5152
5153/// Insert one row, and any rows nested inside it.
5154///
5155/// Hasura writes a parent and its children in one mutation:
5156///
5157/// ```graphql
5158/// insert_article(objects: [{title: "x", author: {data: {name: "y"}}}])
5159/// ```
5160///
5161/// Which row goes first follows from which side holds the key. A relationship
5162/// to one row is reached through a column of *this* table, so the related row
5163/// is inserted first and its key is what this row's column is set to. A
5164/// relationship to many rows is reached through a column of *theirs*, so this
5165/// row goes first and each child's column is set from it.
5166///
5167/// Everything runs in the transaction the caller opened, so a child that
5168/// cannot be written takes the parent with it -- which is what writing them in
5169/// one mutation means.
5170fn insert_row<'life>(
5171    conn: &'life mut sqlx::PgConnection,
5172    schema_name: &'life str,
5173    table_name: &'life str,
5174    object: serde_json::Map<String, serde_json::Value>,
5175    context: &'life InsertContext<'life>,
5176    written_count: &'life mut usize,
5177) -> std::pin::Pin<
5178    Box<
5179        dyn std::future::Future<Output = Result<serde_json::Value, async_graphql::Error>>
5180            + Send
5181            + 'life,
5182    >,
5183> {
5184    Box::pin(async move {
5185        use sqlx::Row;
5186
5187        let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5188        let table = context.cache.get_table(&qi).ok_or_else(|| {
5189            async_graphql::Error::new(format!("unknown table \"{}\"", table_name))
5190        })?;
5191        let column_types: HashMap<String, String> = table
5192            .columns
5193            .values()
5194            .map(|c| (c.name.clone(), c.nominal_type.clone()))
5195            .collect();
5196
5197        // A key that is a relationship rather than a column is a nested write.
5198        let type_name = context
5199            .type_names
5200            .get(&(schema_name.to_string(), table_name.to_string()));
5201        let relationships: &[RelationshipField] = type_name
5202            .and_then(|name| context.relationships.get(name))
5203            .map(|r| r.as_slice())
5204            .unwrap_or(&[]);
5205
5206        let mut columns = serde_json::Map::new();
5207        type Nested<'r> = (
5208            &'r RelationshipField,
5209            serde_json::Value,
5210            Option<serde_json::Value>,
5211        );
5212        let mut to_one: Vec<Nested> = Vec::new();
5213        let mut to_many: Vec<Nested> = Vec::new();
5214
5215        for (key, value) in object {
5216            match relationships.iter().find(|r| r.name == key) {
5217                None => {
5218                    // A written value is keyed by the field it was sent under;
5219                    // the statement is written in the table's own columns.
5220                    columns.insert(
5221                        table_column_for(context.names, table, &key).to_string(),
5222                        value,
5223                    );
5224                }
5225                Some(_) if value.is_null() => {
5226                    // `author: null` is a row with no author, not a row whose
5227                    // author is a null object. A client writing one insert for
5228                    // both cases sends the relationship either way.
5229                    continue;
5230                }
5231                Some(relationship) => {
5232                    // `{data: {...}}` for one row, `{data: [{...}]}` for many,
5233                    // and an `on_conflict` beside either -- a nested row is
5234                    // upserted the same way a top-level one is.
5235                    let (data, conflict) = match &value {
5236                        serde_json::Value::Object(map) => (
5237                            map.get("data").cloned().unwrap_or(value.clone()),
5238                            map.get("on_conflict").cloned().filter(|v| !v.is_null()),
5239                        ),
5240                        other => (other.clone(), None),
5241                    };
5242                    // Which row goes first follows from which side holds the
5243                    // key, not from how many rows there are. A one-to-one
5244                    // whose child key *is* the parent's -- `author_detail.id`
5245                    // referencing `author.id` -- is one row either way, and
5246                    // the parent still has to be written before there is a key
5247                    // to give it.
5248                    if child_holds_the_key(&relationship.relationship) {
5249                        to_many.push((relationship, data, conflict));
5250                    } else {
5251                        to_one.push((relationship, data, conflict));
5252                    }
5253                }
5254            }
5255        }
5256
5257        // A shape is checked before it is written, since PostGIS would build
5258        // one from a document that is not a shape.
5259        for (column, value) in &columns {
5260            if matches!(
5261                column_types.get(column).map(String::as_str),
5262                Some("geometry") | Some("geography")
5263            ) {
5264                // Under the name the client wrote it under, which is the
5265                // exposed one rather than the column's where they differ.
5266                check_geojson(value).map_err(|error| {
5267                    let field = context
5268                        .names
5269                        .column(schema_name, table_name, column)
5270                        .unwrap_or(column);
5271                    at_path(error, &format!("{}.{}", context.row, field))
5272                })?;
5273            }
5274        }
5275
5276        // The rows this one points at, first: this row's own column carries
5277        // their key.
5278        for (relationship, data, conflict) in to_one {
5279            let plan =
5280                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5281                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5282            let serde_json::Value::Object(child) = data else {
5283                return Err(async_graphql::Error::new(format!(
5284                    "\"{}\" takes an object to insert",
5285                    relationship.name
5286                )));
5287            };
5288            // Writing the related row is what fills this row's key column in,
5289            // so a value already written there is a second answer to the same
5290            // question and one of them would be silently discarded.
5291            for (local, _) in &plan.columns {
5292                if columns.get(local).is_some_and(|v| !v.is_null()) {
5293                    return Err(at_path(
5294                        validation_error(format!(
5295                            "cannot insert object relationship \"{}\" as \"{}\" column \
5296                             values are already determined",
5297                            relationship.name, local
5298                        )),
5299                        &format!("{}.{}", context.row, relationship.name),
5300                    ));
5301                }
5302            }
5303            // A to-one row is written under the relationship's own name:
5304            // `...objects[0].author.data`, and its `on_conflict` beside it.
5305            let under = format!("{}.{}", context.row, relationship.name);
5306            let nested = InsertContext {
5307                on_conflict: conflict,
5308                cache: context.cache,
5309                relationships: context.relationships,
5310                type_names: context.type_names,
5311                names: context.names,
5312                caller: context.caller,
5313                row: format!("{}.data", under),
5314                objects: format!("{}.data", under),
5315                conflict: format!("{}.on_conflict", under),
5316            };
5317            let written = insert_row(
5318                conn,
5319                &plan.foreign_schema,
5320                &plan.foreign_table,
5321                child,
5322                &nested,
5323                written_count,
5324            )
5325            .await?;
5326            for (local, foreign) in &plan.columns {
5327                if let Some(value) = written.get(foreign) {
5328                    columns.insert(local.clone(), value.clone());
5329                }
5330            }
5331        }
5332
5333        // What the server fills in regardless of what was sent. Applied after
5334        // the nested rows have contributed their keys and before the columns
5335        // are counted, because a preset is a column this statement writes --
5336        // and applied *over* the request, since a preset the client could
5337        // override is not a preset.
5338        for (column, value) in crate::role::presets(
5339            &context.caller,
5340            context.names,
5341            schema_name,
5342            table_name,
5343            crate::role::Verb::Insert,
5344        )
5345        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?
5346        {
5347            columns.insert(column, value);
5348        }
5349
5350        // `ON CONFLICT` says which uniqueness is being resolved against and
5351        // what to do about it. An empty `update_columns` is `DO NOTHING`,
5352        // which is how a client says "leave the row that is already there".
5353        // The columns this statement writes, settled before the conflict
5354        // clause so a predicate on it can number its parameters after theirs.
5355        let column_names: Vec<&str> = columns.keys().map(|k| k.as_str()).collect();
5356        let mut conflict_values: Vec<serde_json::Value> = Vec::new();
5357        let conflict_sql = match &context.on_conflict {
5358            Some(serde_json::Value::Object(spec)) => {
5359                let constraint = spec.get("constraint").and_then(|v| v.as_str());
5360                let Some(constraint) = constraint else {
5361                    return Err(async_graphql::Error::new(
5362                        "on_conflict needs a constraint to resolve against",
5363                    ));
5364                };
5365                if !table
5366                    .unique_constraints
5367                    .iter()
5368                    .any(|(name, _)| name == constraint)
5369                {
5370                    return Err(async_graphql::Error::new(format!(
5371                        "\"{}\" is not a unique constraint of \"{}\"",
5372                        constraint, table_name
5373                    )));
5374                }
5375                let updates: Vec<&str> = spec
5376                    .get("update_columns")
5377                    .and_then(|v| v.as_array())
5378                    .map(|items| items.iter().filter_map(|i| i.as_str()).collect())
5379                    .unwrap_or_default();
5380                // The member that stands for the columns this role has none
5381                // of. It is in the enum so that `on_conflict` exists at all;
5382                // naming it is the one thing it cannot be used for.
5383                if updates.contains(&PLACEHOLDER_COLUMN) {
5384                    return Err(at_path(
5385                        coded_error("validation-failed", "erroneous column name"),
5386                        &context.conflict,
5387                    ));
5388                }
5389
5390                if updates.is_empty() {
5391                    format!(
5392                        " ON CONFLICT ON CONSTRAINT {} DO NOTHING",
5393                        postrust_sql::escape_ident(constraint)
5394                    )
5395                } else {
5396                    // An `ON CONFLICT DO UPDATE` is an update, so the update
5397                    // permission's presets are written here too -- and a
5398                    // column a preset writes does not also take its value from
5399                    // `EXCLUDED`, which is what "a preset overrides the
5400                    // request" means on this side.
5401                    let preset = crate::role::presets(
5402                        &context.caller,
5403                        context.names,
5404                        schema_name,
5405                        table_name,
5406                        crate::role::Verb::Update,
5407                    )
5408                    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5409
5410                    // Numbered after the row's own values and before the
5411                    // predicate below, because `SET` is written before `WHERE`
5412                    // and the parameters are bound in the order they appear.
5413                    let mut param_idx = column_names.len() + 1;
5414                    let mut assignments: Vec<String> =
5415                        Vec::with_capacity(preset.len() + updates.len());
5416                    for (column, value) in &preset {
5417                        assignments.push(format!(
5418                            "{} = {}",
5419                            postrust_sql::escape_ident(column),
5420                            write_expression(
5421                                &column_types,
5422                                column,
5423                                value,
5424                                &format!("${}", param_idx)
5425                            )
5426                        ));
5427                        conflict_values.push(write_operand(&column_types, column, value));
5428                        param_idx += 1;
5429                    }
5430                    for field in &updates {
5431                        let column = table_column_for(context.names, table, field);
5432                        if preset.iter().any(|(written, _)| written == column) {
5433                            continue;
5434                        }
5435                        assignments.push(format!(
5436                            "{} = EXCLUDED.{}",
5437                            postrust_sql::escape_ident(column),
5438                            postrust_sql::escape_ident(column)
5439                        ));
5440                    }
5441                    // `where` on an upsert decides whether the row that is
5442                    // already there is overwritten -- "only if what I am
5443                    // writing is newer". It reads the existing row, which is
5444                    // what the statement's alias names.
5445                    //
5446                    // The update permission's own filter is ANDed into it: an
5447                    // upsert that overwrites a row is an update, and a role
5448                    // that may not update a row may not reach it by inserting
5449                    // over it. Compiling that filter is also where a session
5450                    // variable it names and the caller does not carry is
5451                    // noticed -- which is the answer Hasura gives before it
5452                    // writes anything.
5453                    let granted = permission_filter(
5454                        &context.caller,
5455                        context.names,
5456                        schema_name,
5457                        table_name,
5458                        crate::role::Verb::Update,
5459                    )?;
5460                    let requested = spec.get("where").filter(|f| !f.is_null()).cloned();
5461                    let condition = match and_predicates(requested, granted) {
5462                        Some(filter) => {
5463                            let filter = &filter;
5464                            let type_name = context
5465                                .type_names
5466                                .get(&(schema_name.to_string(), table_name.to_string()))
5467                                .map(String::as_str)
5468                                .unwrap_or(table_name);
5469                            let scope = WhereScope::table(
5470                                schema_name,
5471                                table_name,
5472                                type_name,
5473                                context.names,
5474                            )
5475                            .under_alias(WRITTEN_ROW)
5476                            .with_resolution(context.cache, context.relationships);
5477                            let mut alias_counter = 0usize;
5478                            build_condition(
5479                                filter,
5480                                &scope,
5481                                &mut param_idx,
5482                                &mut conflict_values,
5483                                &mut alias_counter,
5484                            )?
5485                            .map(|sql| format!(" WHERE {}", sql))
5486                            .unwrap_or_default()
5487                        }
5488                        None => String::new(),
5489                    };
5490                    format!(
5491                        " ON CONFLICT ON CONSTRAINT {} DO UPDATE SET {}{}",
5492                        postrust_sql::escape_ident(constraint),
5493                        assignments.join(", "),
5494                        condition
5495                    )
5496                }
5497            }
5498            _ => String::new(),
5499        };
5500
5501        // The row this statement writes, as `RETURNING` names it. Not the
5502        // table's own name: qualifying it reads as a column of a table called
5503        // `public`, which is what `missing FROM-clause entry for table
5504        // "public"` means, and leaving it bare reads as a column of the table
5505        // where one shares the name.
5506        let qualified_table = postrust_sql::escape_ident(WRITTEN_ROW);
5507
5508        // What the written row has to satisfy for the write to stand,
5509        // evaluated in `RETURNING` against the row as written. Its parameters
5510        // are numbered after the values and the conflict clause, since
5511        // `RETURNING` is last.
5512        let check_scope = WhereScope::for_alias(
5513            schema_name,
5514            table_name,
5515            WRITTEN_ROW,
5516            context
5517                .type_names
5518                .get(&(schema_name.to_string(), table_name.to_string()))
5519                .map(String::as_str)
5520                .unwrap_or(table_name),
5521            context.cache,
5522            context.relationships,
5523            context.names,
5524        );
5525        let (check_sql, check_values) = {
5526            let check = crate::role::write_check(
5527                &context.caller,
5528                context.names,
5529                schema_name,
5530                table_name,
5531                crate::role::Verb::Insert,
5532            )
5533            .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5534            match check {
5535                None => (String::new(), Vec::new()),
5536                Some(check) => {
5537                    let mut values = Vec::new();
5538                    let mut param_idx = column_names.len() + conflict_values.len() + 1;
5539                    let mut alias_counter = 0usize;
5540                    match build_condition(
5541                        &check,
5542                        &check_scope,
5543                        &mut param_idx,
5544                        &mut values,
5545                        &mut alias_counter,
5546                    )? {
5547                        None => (String::new(), Vec::new()),
5548                        Some(sql) => (
5549                            format!(
5550                                ", ({}) AS {}",
5551                                sql,
5552                                postrust_sql::escape_ident(CHECK_COLUMN)
5553                            ),
5554                            values,
5555                        ),
5556                    }
5557                }
5558            }
5559        };
5560
5561        let names = column_names;
5562        let written = if names.is_empty() {
5563            // Every column defaulted. `DEFAULT VALUES` is how SQL says that;
5564            // an empty column list is a syntax error.
5565            let sql = format!(
5566                "INSERT INTO {}.{} AS {} DEFAULT VALUES{} RETURNING {}{}",
5567                postrust_sql::escape_ident(schema_name),
5568                postrust_sql::escape_ident(table_name),
5569                qualified_table,
5570                conflict_sql,
5571                row_json(&qualified_table, &column_types),
5572                check_sql
5573            );
5574            let mut query = sqlx::query(&sql);
5575            for value in &conflict_values {
5576                query = bind_json_value(query, value);
5577            }
5578            for value in &check_values {
5579                query = bind_json_value(query, value);
5580            }
5581            query
5582                .fetch_optional(&mut *conn)
5583                .await
5584                // A constraint the write violated is reported against the rows
5585                // the statement was given, which is where Hasura reports it.
5586                .map_err(|error| at_path(database_error(error), &context.objects))?
5587        } else {
5588            let placeholders: Vec<String> = names
5589                .iter()
5590                .enumerate()
5591                .map(|(i, column)| {
5592                    let placeholder = format!("${}", i + 1);
5593                    match columns.get(*column) {
5594                        Some(value) => write_expression(&column_types, column, value, &placeholder),
5595                        None => placeholder,
5596                    }
5597                })
5598                .collect();
5599            let sql = format!(
5600                "INSERT INTO {}.{} AS {} ({}) VALUES ({}){} RETURNING {}{}",
5601                postrust_sql::escape_ident(schema_name),
5602                postrust_sql::escape_ident(table_name),
5603                qualified_table,
5604                names
5605                    .iter()
5606                    .map(|c| postrust_sql::escape_ident(c))
5607                    .collect::<Vec<_>>()
5608                    .join(", "),
5609                placeholders.join(", "),
5610                conflict_sql,
5611                row_json(&qualified_table, &column_types),
5612                check_sql
5613            );
5614            trace!("Executing INSERT SQL: {}", sql);
5615            let mut query = sqlx::query(&sql);
5616            for column in &names {
5617                if let Some(value) = columns.get(*column) {
5618                    query = bind_json_value(query, &write_operand(&column_types, column, value));
5619                }
5620            }
5621            for value in &conflict_values {
5622                query = bind_json_value(query, value);
5623            }
5624            for value in &check_values {
5625                query = bind_json_value(query, value);
5626            }
5627            query
5628                .fetch_optional(&mut *conn)
5629                .await
5630                // A constraint the write violated is reported against the rows
5631                // the statement was given, which is where Hasura reports it.
5632                .map_err(|error| at_path(database_error(error), &context.objects))?
5633        };
5634
5635        // `DO NOTHING` writes nothing and returns nothing, which is the answer
5636        // rather than an error: the row that was already there stays.
5637        let Some(written) = written else {
5638            return Ok(serde_json::Value::Null);
5639        };
5640        refuse_unchecked_rows(std::slice::from_ref(&written), !check_sql.is_empty())
5641            .map_err(|error| at_path(error, &context.objects))?;
5642        let row: serde_json::Value = written.try_get(0).unwrap_or(serde_json::Value::Null);
5643        *written_count += 1;
5644
5645        // Then the rows that point at this one, which need its key.
5646        for (relationship, data, conflict) in to_many {
5647            let plan =
5648                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5649                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5650            let children = match data {
5651                serde_json::Value::Array(items) => items,
5652                serde_json::Value::Object(map) => vec![serde_json::Value::Object(map)],
5653                serde_json::Value::Null => Vec::new(),
5654                _ => {
5655                    return Err(async_graphql::Error::new(format!(
5656                        "\"{}\" takes objects to insert",
5657                        relationship.name
5658                    )))
5659                }
5660            };
5661            for (child_index, child) in children.into_iter().enumerate() {
5662                let serde_json::Value::Object(mut child) = child else {
5663                    continue;
5664                };
5665                for (local, foreign) in &plan.columns {
5666                    // The parent's key is what makes the child a child, so a
5667                    // child that writes it too is asking for a different
5668                    // parent than the one it is nested inside.
5669                    if child.get(foreign).is_some_and(|v| !v.is_null()) {
5670                        return Err(at_path(
5671                            validation_error(format!(
5672                                "cannot insert \"{}\" columns as their values are already \
5673                                 being determined by parent insert",
5674                                foreign
5675                            )),
5676                            &format!(
5677                                "{}.{}{}",
5678                                context.row,
5679                                relationship.name,
5680                                match relationship.is_list {
5681                                    true => format!(".data[{}]", child_index),
5682                                    false => ".data".to_string(),
5683                                }
5684                            ),
5685                        ));
5686                    }
5687                    if let Some(value) = row.get(local) {
5688                        child.insert(foreign.clone(), value.clone());
5689                    }
5690                }
5691                // `...objects[0].articles.data[0]`, which is the path
5692                // Hasura answers for a child that writes its parent's key.
5693                // One row written after its parent -- a mapped object
5694                // relationship with `after_parent` -- has no index: there is
5695                // no list for it to be an item of.
5696                let at = match relationship.is_list {
5697                    true => format!(".data[{}]", child_index),
5698                    false => ".data".to_string(),
5699                };
5700                let under = format!("{}.{}", context.row, relationship.name);
5701                let nested = InsertContext {
5702                    on_conflict: conflict.clone(),
5703                    cache: context.cache,
5704                    relationships: context.relationships,
5705                    type_names: context.type_names,
5706                    names: context.names,
5707                    caller: context.caller,
5708                    row: format!("{}{}", under, at),
5709                    objects: format!("{}.data", under),
5710                    conflict: format!("{}.on_conflict", under),
5711                };
5712                insert_row(
5713                    conn,
5714                    &plan.foreign_schema,
5715                    &plan.foreign_table,
5716                    child,
5717                    &nested,
5718                    written_count,
5719                )
5720                .await?;
5721            }
5722        }
5723
5724        Ok(row)
5725    })
5726}
5727
5728/// What a nested insert needs to follow a relationship.
5729///
5730/// Cloned per row and per nested row, which costs three strings: the paths are
5731/// the only part that differs between them, and the rest is references and a
5732/// `Caller`.
5733#[derive(Clone)]
5734struct InsertContext<'a> {
5735    /// What to do when this row conflicts. A nested row carries its own, from
5736    /// the `on_conflict` beside its `data`.
5737    on_conflict: Option<serde_json::Value>,
5738    cache: &'a SchemaCache,
5739    relationships: &'a HashMap<String, Vec<RelationshipField>>,
5740    /// (schema, table) -> the GraphQL type name, which keys the relationship
5741    /// map. A table's type is not always its name: a second schema prefixes
5742    /// it, and a name may have been given.
5743    type_names: &'a HashMap<(String, String), String>,
5744    /// The names columns are exposed under, so a written value keyed by a
5745    /// field reaches the column it names.
5746    names: &'a crate::names::NameOverrides,
5747    /// Who is writing, so that a permission's presets and its check reach
5748    /// every row this insert writes -- including the nested ones, which are
5749    /// rows of other tables under other rules.
5750    caller: crate::role::Caller<'a>,
5751    /// Where this row is in the request, as Hasura writes such a place:
5752    /// `$.selectionSet.insert_author.args.objects[0]`, and one level down
5753    /// `$.selectionSet.insert_author.args.objects[0].articles.data[0]`. A
5754    /// column of it is that with the field's name appended.
5755    row: String,
5756    /// Where the rows this statement writes were named. What a refused `check`
5757    /// is reported against -- the argument, not the row inside it, which is
5758    /// what Hasura answers.
5759    objects: String,
5760    /// Where this statement's `on_conflict` was named.
5761    conflict: String,
5762}
5763
5764/// Re-read written rows so that a mutation's `returning` can carry
5765/// relationships and computed fields.
5766///
5767/// `RETURNING row_to_json(t.*)` gives the table's own columns and nothing
5768/// else, so a relationship asked for beside them had no value to resolve --
5769/// and a non-null list field with no value is an error, which meant a mutation
5770/// that had written its rows correctly answered as though it had failed. The
5771/// write is not in doubt; only the shape of the answer is.
5772///
5773/// So the rows are read again by their primary key, through the same
5774/// projection an ordinary query uses. One extra round trip, and only when the
5775/// selection actually asks for something `RETURNING` cannot give.
5776#[allow(clippy::too_many_arguments)]
5777async fn reread_returning(
5778    pool: &PgPool,
5779    gql_ctx: &GraphQLContext,
5780    schema_name: &str,
5781    table_name: &str,
5782    type_name: &str,
5783    rows: Vec<Value>,
5784    returning: async_graphql::SelectionField<'_>,
5785    relationships: &HashMap<String, Vec<RelationshipField>>,
5786    names: &crate::names::NameOverrides,
5787    max_rows: Option<i64>,
5788) -> Result<Vec<Value>, async_graphql::Error> {
5789    let guard = gql_ctx
5790        .schema_cache
5791        .get()
5792        .await
5793        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5794    let Some(cache) = guard.as_ref() else {
5795        return Ok(rows);
5796    };
5797    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5798    let Some(table) = cache.get_table(&qi) else {
5799        return Ok(rows);
5800    };
5801    if table.pk_cols.is_empty() {
5802        // Nothing to identify the rows by. The columns are still right.
5803        return Ok(rows);
5804    }
5805
5806    let mut param_idx = 1usize;
5807    let mut values: Vec<serde_json::Value> = Vec::new();
5808    let mut alias_counter = 0usize;
5809    let embeds = build_embed_expressions(
5810        &gql_ctx.caller(),
5811        cache,
5812        relationships,
5813        type_name,
5814        "src",
5815        returning.selection_set(),
5816        max_rows,
5817        &mut alias_counter,
5818        &mut param_idx,
5819        &mut values,
5820        names,
5821    )?;
5822    let computed = computed_projections(
5823        table,
5824        returning.selection_set(),
5825        "src",
5826        names,
5827        cache,
5828        &mut param_idx,
5829        &mut values,
5830    )?;
5831    // A rename is reason enough to read the rows again: `RETURNING` answers in
5832    // the table's own column names, which are not the names the client asked
5833    // under.
5834    let renames = names.renames_columns(&table.schema, &table.name);
5835    if embeds.is_empty() && computed.is_empty() && !renames {
5836        return Ok(rows);
5837    }
5838
5839    // One row per key written, in the order they were written.
5840    let mut conditions = Vec::with_capacity(rows.len());
5841    for row in &rows {
5842        let mut parts = Vec::with_capacity(table.pk_cols.len());
5843        for column in &table.pk_cols {
5844            let Value::Object(map) = row else {
5845                return Ok(rows);
5846            };
5847            let Some(value) = map.get(&async_graphql::Name::new(column)) else {
5848                return Ok(rows);
5849            };
5850            parts.push(format!(
5851                "{} = ${}",
5852                postrust_sql::escape_ident(column),
5853                param_idx
5854            ));
5855            values.push(value_to_json(value));
5856            param_idx += 1;
5857        }
5858        conditions.push(format!("({})", parts.join(" AND ")));
5859    }
5860    if conditions.is_empty() {
5861        return Ok(rows);
5862    }
5863
5864    let mut projection =
5865        rename_projection(table, "src", names).unwrap_or_else(|| "src.*".to_string());
5866    for expression in &computed {
5867        projection.push_str(", ");
5868        projection.push_str(expression);
5869    }
5870    for (name, expression) in &embeds {
5871        projection.push_str(", ");
5872        projection.push_str(expression);
5873        projection.push_str(" AS ");
5874        projection.push_str(&postrust_sql::escape_ident(name));
5875    }
5876
5877    let sql = format!(
5878        "SELECT {} FROM (SELECT {} FROM (SELECT * FROM {}.{} WHERE {}) AS src) AS pgrst_r",
5879        row_json("pgrst_r", &exposed_column_types(table, names)),
5880        projection,
5881        postrust_sql::escape_ident(schema_name),
5882        postrust_sql::escape_ident(table_name),
5883        conditions.join(" OR ")
5884    );
5885
5886    // The same transaction the write went into: the rows it is reading are the
5887    // rows that write produced, and nothing has committed yet.
5888    let mut guard = write_tx(gql_ctx, pool).await?;
5889    let conn = guard.as_mut().expect("write_tx opens one");
5890    let reread = execute_query_on(conn, &sql, &values).await?;
5891    drop(guard);
5892
5893    if reread.is_empty() {
5894        return Ok(rows);
5895    }
5896    Ok(reread.into_iter().map(json_to_value).collect())
5897}
5898
5899/// Execute an insert mutation.
5900async fn execute_insert(
5901    pool: &PgPool,
5902    gql_ctx: &GraphQLContext,
5903    schema_name: &str,
5904    table_name: &str,
5905    objects: serde_json::Value,
5906    context: &InsertContext<'_>,
5907    // Whether the client wrote one row rather than a list of them. Only the
5908    // error paths care: `insert_x_one(object: {...})` has no row to index, so
5909    // its rows are named `args.object` and not `args.object[0]`.
5910    single: bool,
5911) -> Result<(Vec<Value>, usize), async_graphql::Error> {
5912    trace!("Insert mutation for {}: {:?}", table_name, objects);
5913
5914    // Handle both array and single object
5915    let objects_array = match objects {
5916        serde_json::Value::Array(arr) => arr,
5917        serde_json::Value::Object(obj) => vec![serde_json::Value::Object(obj)],
5918        _ => {
5919            return Err(async_graphql::Error::new(
5920                "objects must be an array or object",
5921            ))
5922        }
5923    };
5924
5925    if objects_array.is_empty() {
5926        return Err(async_graphql::Error::new("objects cannot be empty"));
5927    }
5928
5929    let mut guard = write_tx(gql_ctx, pool).await?;
5930    let conn = guard.as_mut().expect("write_tx opens one");
5931
5932    let mut inserted: Vec<Value> = Vec::new();
5933    let mut written = 0usize;
5934
5935    for (index, object) in objects_array.into_iter().enumerate() {
5936        let serde_json::Value::Object(map) = object else {
5937            return Err(async_graphql::Error::new(
5938                "each object to insert is an object",
5939            ));
5940        };
5941        // This row's own place in the request, which is what an error inside
5942        // it is reported against.
5943        let context = InsertContext {
5944            row: match single {
5945                true => context.objects.clone(),
5946                false => format!("{}[{}]", context.objects, index),
5947            },
5948            ..context.clone()
5949        };
5950        let row = insert_row(conn, schema_name, table_name, map, &context, &mut written).await?;
5951        // A row `DO NOTHING` left alone is not in `returning` and is not in
5952        // `affected_rows` either: nothing was written, and the row that was
5953        // already there is not this mutation's to report.
5954        if !row.is_null() {
5955            inserted.push(json_to_value(row));
5956        }
5957    }
5958
5959    Ok((inserted, written))
5960}
5961
5962/// Shape a mutation's result for the field that asked for it.
5963///
5964/// A by-key mutation answers with the row, or with null where the key matched
5965/// nothing. Every other mutation answers with the table's mutation response,
5966/// which carries `affected_rows` alongside the rows themselves -- a client may
5967/// ask for no rows back and still need the count, which is the usual case for
5968/// a delete.
5969///
5970/// The count is passed in rather than taken from the rows, because a nested
5971/// insert writes more rows than it returns: two articles each carrying a new
5972/// author is four rows written and two returned, and four is the answer.
5973fn mutation_result<'a>(rows: Vec<Value>, affected: usize, by_key: bool) -> Option<FieldValue<'a>> {
5974    if by_key {
5975        return rows.into_iter().next().map(FieldValue::value);
5976    }
5977    let mut response = async_graphql::indexmap::IndexMap::new();
5978    response.insert(
5979        async_graphql::Name::new("affected_rows"),
5980        Value::from(affected),
5981    );
5982    response.insert(async_graphql::Name::new("returning"), Value::List(rows));
5983    Some(FieldValue::value(Value::Object(response)))
5984}
5985
5986/// Bind a JSON value to a sqlx query.
5987fn bind_json_value<'q>(
5988    query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
5989    value: &serde_json::Value,
5990) -> sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments> {
5991    match value {
5992        serde_json::Value::Null => query.bind(None::<String>),
5993        serde_json::Value::Bool(b) => query.bind(*b),
5994        serde_json::Value::Number(n) => {
5995            if let Some(i) = n.as_i64() {
5996                query.bind(i)
5997            } else if let Some(f) = n.as_f64() {
5998                query.bind(f)
5999            } else {
6000                query.bind(n.to_string())
6001            }
6002        }
6003        serde_json::Value::String(s) => query.bind(s.clone()),
6004        _ => query.bind(value.to_string()),
6005    }
6006}
6007
6008/// Execute an update mutation.
6009#[allow(clippy::too_many_arguments)] // the predicate needs the schema it reads
6010async fn execute_update(
6011    pool: &PgPool,
6012    gql_ctx: &GraphQLContext,
6013    schema_name: &str,
6014    table_name: &str,
6015    type_name: &str,
6016    operators: Vec<(&'static str, serde_json::Value)>,
6017    column_types: HashMap<String, String>,
6018    where_clause: Option<serde_json::Value>,
6019    relationships: &HashMap<String, Vec<RelationshipField>>,
6020    names: &crate::names::NameOverrides,
6021) -> Result<Vec<Value>, async_graphql::Error> {
6022    use sqlx::Row;
6023
6024    trace!("Update mutation for {}: {:?}", table_name, operators);
6025
6026    // Build the SET clause. Each operator writes a column in terms of itself
6027    // except `_set`, which replaces it -- `_inc` adds, the jsonb operators
6028    // concatenate or remove, and a client may send several in one mutation as
6029    // long as they name different columns.
6030    let mut set_parts: Vec<String> = Vec::new();
6031    let mut set_values: Vec<serde_json::Value> = Vec::new();
6032    let mut param_idx = 1;
6033    let mut written: HashSet<String> = HashSet::new();
6034
6035    // What the permission writes whatever the request said. A preset overrides
6036    // rather than collides -- one a client could override is not a preset --
6037    // so these go in first and the operators below step around the columns
6038    // they name.
6039    //
6040    // This is also what makes an update with no `_set` at all a real update:
6041    // `update_resident(where: {...})` under a permission whose `set` names
6042    // `city` changes `city`, and answers with the rows it changed rather than
6043    // with nothing.
6044    let preset = crate::role::presets(
6045        &gql_ctx.caller(),
6046        names,
6047        schema_name,
6048        table_name,
6049        crate::role::Verb::Update,
6050    )
6051    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6052    let preset_columns: HashSet<&str> = preset.iter().map(|(name, _)| name.as_str()).collect();
6053    for (column, value) in &preset {
6054        let placeholder =
6055            write_expression(&column_types, column, value, &format!("${}", param_idx));
6056        set_parts.push(format!(
6057            "{} = {}",
6058            postrust_sql::escape_ident(column),
6059            placeholder
6060        ));
6061        set_values.push(write_operand(&column_types, column, value));
6062        param_idx += 1;
6063    }
6064
6065    for (operator, payload) in &operators {
6066        let serde_json::Value::Object(map) = payload else {
6067            return Err(async_graphql::Error::new(format!(
6068                "\"{}\" takes an object mapping columns to values",
6069                operator
6070            )));
6071        };
6072        for (field, value) in map {
6073            let column = &names
6074                .column_source(schema_name, table_name, field)
6075                .unwrap_or(field)
6076                .to_string();
6077            // A preset has this column. Not the duplicate-write error below:
6078            // the request is allowed to name it, and the permission wins.
6079            if preset_columns.contains(column.as_str()) {
6080                continue;
6081            }
6082            if !written.insert(column.clone()) {
6083                return Err(async_graphql::Error::new(format!(
6084                    "\"{}\" is written twice in one update; a column may be \
6085                     changed by one operator at a time",
6086                    column
6087                )));
6088            }
6089            let quoted = postrust_sql::escape_ident(column);
6090            // `_set` writes the column and `_inc` adds to it, so both take a
6091            // value of the column's type and are cast to it -- `money + $1`
6092            // is not an operator PostgreSQL has, and neither is `money +
6093            // double precision`, which is what a bound number arrives as. The
6094            // others already say what they need -- `::jsonb` for a
6095            // concatenation, `::text[]` for a path -- and casting twice would
6096            // be wrong for `_delete_elem`, whose operand is an integer rather
6097            // than a value of the column.
6098            let placeholder = match *operator {
6099                "_set" | "_inc" => {
6100                    write_expression(&column_types, column, value, &format!("${}", param_idx))
6101                }
6102                _ => format!("${}", param_idx),
6103            };
6104            // The assignment PostgreSQL needs, which for everything but `_set`
6105            // reads the column's current value.
6106            let assignment = match *operator {
6107                "_set" => format!("{} = {}", quoted, placeholder),
6108                "_inc" => format!("{} = {} + {}", quoted, quoted, placeholder),
6109                "_append" => format!("{} = {} || {}::jsonb", quoted, quoted, placeholder),
6110                "_prepend" => format!("{} = {}::jsonb || {}", quoted, placeholder, quoted),
6111                "_delete_key" => format!("{} = {} - {}::text", quoted, quoted, placeholder),
6112                "_delete_elem" => format!("{} = {} - {}::int", quoted, quoted, placeholder),
6113                // A path is a list of keys, and one parameter carrying
6114                // `["name","last"]` is a JSON array -- which PostgreSQL reads
6115                // as an array literal and refuses, `malformed array literal`.
6116                // The elements are bound one at a time into an `ARRAY[...]`,
6117                // the same way the key-existence comparisons take theirs.
6118                "_delete_at_path" => {
6119                    let Some(steps) = value.as_array() else {
6120                        return Err(async_graphql::Error::new(format!(
6121                            "\"_delete_at_path\" on \"{}\" takes a list of keys",
6122                            column
6123                        )));
6124                    };
6125                    let path = sql_array(steps, "text[]", &mut param_idx, &mut set_values);
6126                    set_parts.push(format!("{} = {} #- {}", quoted, quoted, path));
6127                    continue;
6128                }
6129                other => {
6130                    return Err(async_graphql::Error::new(format!(
6131                        "unsupported update operator \"{}\"",
6132                        other
6133                    )))
6134                }
6135            };
6136            if *operator == "_set"
6137                && matches!(
6138                    column_types.get(column).map(String::as_str),
6139                    Some("geometry") | Some("geography")
6140                )
6141            {
6142                check_geojson(value)?;
6143            }
6144            set_parts.push(assignment);
6145            set_values.push(match *operator {
6146                "_set" => write_operand(&column_types, column, value),
6147                _ => value.clone(),
6148            });
6149            param_idx += 1;
6150        }
6151    }
6152
6153    // An update that changes nothing changes no rows. Refusing it looks like
6154    // the stricter answer and is the wrong one: a client building `_set` from
6155    // a form the user submitted unchanged sends an empty object, and the
6156    // honest report of what happened is that nothing did.
6157    if set_parts.is_empty() {
6158        return Ok(Vec::new());
6159    }
6160
6161    // Build WHERE clause. It can follow a relationship, the same way a query's
6162    // can: `update_article(where: {author: {name: {_eq: "x"}}})` names the
6163    // rows by something they point at rather than by a column of their own.
6164    let guard = gql_ctx
6165        .schema_cache
6166        .get()
6167        .await
6168        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6169    let cache = guard
6170        .as_ref()
6171        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6172    let scope = WhereScope::table(schema_name, table_name, type_name, names)
6173        .under_alias(WRITTEN_ROW)
6174        .with_resolution(cache, relationships);
6175    let (where_sql, where_values) = build_where_clause(where_clause.as_ref(), param_idx, &scope)?;
6176
6177    // An absent or unrecognised `where` argument yields an empty clause, which
6178    // would update every row in the table. Refuse instead.
6179    if where_sql.is_empty() {
6180        return Err(async_graphql::Error::new(format!(
6181            "update on \"{}\" requires a `where` argument with at least one \
6182             recognised condition; refusing to update every row",
6183            table_name
6184        )));
6185    }
6186
6187    // What the changed row has to satisfy for the change to stand. Evaluated
6188    // in the `RETURNING` clause, against the row as written -- which is the
6189    // only place it can honestly be evaluated, and costs neither a re-read nor
6190    // a way to identify the rows afterwards.
6191    let (check_sql, check_values) = permission_check_sql(
6192        gql_ctx,
6193        names,
6194        schema_name,
6195        table_name,
6196        crate::role::Verb::Update,
6197        &scope,
6198        param_idx + where_values.len(),
6199    )?;
6200
6201    let sql = format!(
6202        "UPDATE {}.{} AS {} SET {} {} RETURNING {}{}",
6203        postrust_sql::escape_ident(schema_name),
6204        postrust_sql::escape_ident(table_name),
6205        postrust_sql::escape_ident(WRITTEN_ROW),
6206        set_parts.join(", "),
6207        where_sql,
6208        row_json(&postrust_sql::escape_ident(WRITTEN_ROW), &column_types),
6209        check_sql
6210    );
6211
6212    trace!("Executing UPDATE SQL: {}", sql);
6213
6214    // Build query with parameters
6215    let mut query = sqlx::query(&sql);
6216
6217    // Bind SET values
6218    for val in &set_values {
6219        query = bind_json_value(query, val);
6220    }
6221
6222    // Bind WHERE values
6223    for val in &where_values {
6224        query = bind_json_value(query, val);
6225    }
6226
6227    // And the check's, last, because `RETURNING` is.
6228    for val in &check_values {
6229        query = bind_json_value(query, val);
6230    }
6231
6232    let mut guard = write_tx(gql_ctx, pool).await?;
6233    let conn = guard.as_mut().expect("write_tx opens one");
6234    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6235
6236    refuse_unchecked_rows(&rows, !check_sql.is_empty())?;
6237
6238    let updated: Vec<Value> = rows
6239        .iter()
6240        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6241        .map(json_to_value)
6242        .collect();
6243
6244    Ok(updated)
6245}
6246
6247/// A permission's check, as a column of the write's `RETURNING`.
6248///
6249/// Empty where nothing has to be checked. Otherwise `, (<predicate>) AS ...`,
6250/// evaluated per written row against the row itself -- so a row the permission
6251/// refuses is known before the transaction is settled, and settling it is
6252/// already a rollback once an error is reported.
6253fn permission_check_sql(
6254    gql_ctx: &GraphQLContext,
6255    names: &crate::names::NameOverrides,
6256    schema_name: &str,
6257    table_name: &str,
6258    verb: crate::role::Verb,
6259    scope: &WhereScope<'_>,
6260    start_param_idx: usize,
6261) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6262    let check = crate::role::write_check(&gql_ctx.caller(), names, schema_name, table_name, verb)
6263        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6264    let Some(check) = check else {
6265        return Ok((String::new(), Vec::new()));
6266    };
6267
6268    let mut values = Vec::new();
6269    let mut param_idx = start_param_idx;
6270    let mut alias_counter = 0usize;
6271    let condition = build_condition(
6272        &check,
6273        scope,
6274        &mut param_idx,
6275        &mut values,
6276        &mut alias_counter,
6277    )?;
6278    Ok(match condition {
6279        // A check that compiles to nothing restricts nothing, which is what an
6280        // empty one says.
6281        None => (String::new(), Vec::new()),
6282        Some(sql) => (
6283            format!(
6284                ", ({}) AS {}",
6285                sql,
6286                postrust_sql::escape_ident(CHECK_COLUMN)
6287            ),
6288            values,
6289        ),
6290    })
6291}
6292
6293/// The column a permission's check is returned in.
6294const CHECK_COLUMN: &str = "pgrst_permission_check";
6295
6296/// Refuse the whole write if any row it produced fails the permission's check.
6297///
6298/// Every row, not the first: a mutation is one transaction and a single
6299/// refused row takes the rest with it, which is what makes a check a rule
6300/// about the write rather than about a row. A null counts as a failure -- an
6301/// unknown is not a satisfied condition.
6302fn refuse_unchecked_rows(
6303    rows: &[sqlx::postgres::PgRow],
6304    checked: bool,
6305) -> Result<(), async_graphql::Error> {
6306    if !checked {
6307        return Ok(());
6308    }
6309    use sqlx::Row;
6310    let all_passed = rows
6311        .iter()
6312        .all(|row| row.try_get::<bool, _>(CHECK_COLUMN).unwrap_or(false));
6313    match all_passed {
6314        true => Ok(()),
6315        false => Err(coded_error("permission-error", crate::role::CHECK_FAILED)),
6316    }
6317}
6318
6319/// Execute a delete mutation.
6320#[allow(clippy::too_many_arguments)] // the projection needs the schema it reads
6321async fn execute_delete(
6322    pool: &PgPool,
6323    gql_ctx: &GraphQLContext,
6324    schema_name: &str,
6325    table_name: &str,
6326    type_name: &str,
6327    where_clause: Option<serde_json::Value>,
6328    returning: Option<async_graphql::SelectionField<'_>>,
6329    relationships: &HashMap<String, Vec<RelationshipField>>,
6330    names: &crate::names::NameOverrides,
6331    max_rows: Option<i64>,
6332) -> Result<Vec<Value>, async_graphql::Error> {
6333    use sqlx::Row;
6334
6335    trace!("Delete mutation for {}", table_name);
6336
6337    let guard = gql_ctx
6338        .schema_cache
6339        .get()
6340        .await
6341        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6342    let cache = guard
6343        .as_ref()
6344        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6345    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
6346    let column_types = cache
6347        .get_table(&qi)
6348        .map(|table| exposed_column_types(table, names))
6349        .unwrap_or_default();
6350
6351    // Build WHERE clause. It can follow a relationship -- `delete_article(where:
6352    // {author: {id: {_eq: 2}}})` is the article-by-author delete anyone writes
6353    // -- so the scope is given the schema to resolve one with.
6354    let scope = WhereScope::table(schema_name, table_name, type_name, names)
6355        .under_alias(WRITTEN_ROW)
6356        .with_resolution(cache, relationships);
6357    let (where_sql, mut values) = build_where_clause(where_clause.as_ref(), 1, &scope)?;
6358
6359    // An absent or unrecognised `where` argument yields an empty clause, which
6360    // would delete every row in the table. Refuse instead.
6361    if where_sql.is_empty() {
6362        return Err(async_graphql::Error::new(format!(
6363            "delete on \"{}\" requires a `where` argument with at least one \
6364             recognised condition; refusing to delete every row",
6365            table_name
6366        )));
6367    }
6368
6369    // A relationship or computed field asked for in `returning` cannot be read
6370    // afterwards -- the rows are gone by then, which is why this used to answer
6371    // the plain columns and then fail on a non-null list. Inside one statement
6372    // the deleted rows are still a table: the delete goes in a CTE and the
6373    // projection reads from it, while the rows it points at are still there.
6374    let mut projection = String::new();
6375    let renamed = cache
6376        .get_table(&qi)
6377        .and_then(|table| rename_projection(table, "src", names));
6378    if let Some(returning) = returning {
6379        let mut param_idx = values.len() + 1;
6380        let mut alias_counter = 0usize;
6381        let embeds = build_embed_expressions(
6382            &gql_ctx.caller(),
6383            cache,
6384            relationships,
6385            type_name,
6386            "src",
6387            returning.selection_set(),
6388            max_rows,
6389            &mut alias_counter,
6390            &mut param_idx,
6391            &mut values,
6392            names,
6393        )?;
6394        let computed = match cache.get_table(&qi) {
6395            Some(table) => computed_projections(
6396                table,
6397                returning.selection_set(),
6398                "src",
6399                names,
6400                cache,
6401                &mut param_idx,
6402                &mut values,
6403            )?,
6404            None => Vec::new(),
6405        };
6406        for expression in &computed {
6407            projection.push_str(", ");
6408            projection.push_str(expression);
6409        }
6410        for (name, expression) in &embeds {
6411            projection.push_str(", ");
6412            projection.push_str(expression);
6413            projection.push_str(" AS ");
6414            projection.push_str(&postrust_sql::escape_ident(name));
6415        }
6416    }
6417
6418    let written = postrust_sql::escape_ident(WRITTEN_ROW);
6419    let sql = if projection.is_empty() && renamed.is_none() {
6420        format!(
6421            "DELETE FROM {}.{} AS {} {} RETURNING {}",
6422            postrust_sql::escape_ident(schema_name),
6423            postrust_sql::escape_ident(table_name),
6424            written,
6425            where_sql,
6426            row_json(&written, &column_types)
6427        )
6428    } else {
6429        format!(
6430            "WITH pgrst_deleted AS (DELETE FROM {}.{} AS {} {} RETURNING {}.*) \
6431             SELECT {} FROM (SELECT {}{} FROM pgrst_deleted AS src) AS pgrst_r",
6432            postrust_sql::escape_ident(schema_name),
6433            postrust_sql::escape_ident(table_name),
6434            written,
6435            where_sql,
6436            written,
6437            row_json("pgrst_r", &column_types),
6438            renamed.unwrap_or_else(|| "src.*".to_string()),
6439            projection
6440        )
6441    };
6442
6443    trace!("Executing DELETE SQL: {}", sql);
6444
6445    let mut query = sqlx::query(&sql);
6446    for val in &values {
6447        query = bind_json_value(query, val);
6448    }
6449
6450    let mut guard = write_tx(gql_ctx, pool).await?;
6451    let conn = guard.as_mut().expect("write_tx opens one");
6452    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6453
6454    let deleted: Vec<Value> = rows
6455        .iter()
6456        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6457        .map(json_to_value)
6458        .collect();
6459
6460    Ok(deleted)
6461}
6462
6463/// The predicate a role's permission adds to every read of one table.
6464///
6465/// `None` where nothing restricts the read: no permission document, an
6466/// administrator, or a permission whose filter is `{}`. The value is a
6467/// `<table>_bool_exp` like any other, because [`crate::role::permission_where`]
6468/// rewrites it into one -- which is what lets the same compiler serve both and
6469/// keeps a permission able to say everything a `where` can.
6470fn permission_predicate(
6471    caller: &crate::role::Caller<'_>,
6472    names: &crate::names::NameOverrides,
6473    schema: &str,
6474    table: &str,
6475) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6476    permission_filter(caller, names, schema, table, crate::role::Verb::Select)
6477}
6478
6479/// The same, for whichever grant the operation is asking under.
6480fn permission_filter(
6481    caller: &crate::role::Caller<'_>,
6482    names: &crate::names::NameOverrides,
6483    schema: &str,
6484    table: &str,
6485    verb: crate::role::Verb,
6486) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6487    crate::role::row_filter(caller, names, schema, table, verb)
6488        .map(|filter| filter.map(|predicate| serde_json::json!({ PERMISSION: predicate })))
6489        .map_err(|fault| coded_error(fault.code(), fault.to_string()))
6490}
6491
6492/// The key a permission's own predicate is wrapped in on its way to the
6493/// compiler.
6494///
6495/// The two halves of a `where` are the same language and are compiled by the
6496/// same code, which is what keeps a permission able to say everything a client
6497/// can. They are not evaluated by the same party: a client's half asks what
6498/// *this caller* may see, and a permission's half is the server's own
6499/// predicate, evaluated as the server.
6500///
6501/// The difference shows on a relationship. `{"Artist": {"id":
6502/// "X-Hasura-Artist-Id"}}` on `Track` grants the tracks belonging to the
6503/// caller's artist -- to a role that has no permission on `Artist` at all, and
6504/// is not meant to have one: the predicate consults that table, it does not
6505/// expose it. Compiled as the caller, it needs a permission that does not
6506/// exist and refuses itself; compiled as the server, it says what it was
6507/// written to say. `_exists` has always worked this way and says so where it
6508/// is built; this is the same rule for the relationship a permission follows.
6509///
6510/// The name cannot collide with a column: no boolean expression a client can
6511/// write has this field, because no input type declares it.
6512const PERMISSION: &str = "_permission";
6513
6514/// How many rows a permission lets one read return.
6515///
6516/// A ceiling rather than a default: a request asking for more gets this, one
6517/// asking for fewer gets what it asked for, and one asking for nothing gets
6518/// the ceiling. The same rule `PGRST_MAX_ROWS` follows, folded with it.
6519fn permission_limit(
6520    caller: &crate::role::Caller<'_>,
6521    names: &crate::names::NameOverrides,
6522    schema: &str,
6523    table: &str,
6524) -> Option<i64> {
6525    crate::role::read_limit(caller, names, schema, table)
6526}
6527
6528/// Both predicates, as one.
6529///
6530/// Written as an `_and` rather than as two clauses so that everything
6531/// downstream -- parameter numbering, relationship resolution, the `EXISTS`
6532/// that a relationship predicate becomes -- happens once, in one place, for
6533/// both halves.
6534fn and_predicates(
6535    request: Option<serde_json::Value>,
6536    permission: Option<serde_json::Value>,
6537) -> Option<serde_json::Value> {
6538    match (request, permission) {
6539        (None, None) => None,
6540        (Some(only), None) | (None, Some(only)) => Some(only),
6541        (Some(request), Some(permission)) => {
6542            Some(serde_json::json!({ "_and": [request, permission] }))
6543        }
6544    }
6545}
6546
6547/// Build a WHERE clause from a boolean expression.
6548///
6549/// The expression is the JSON form of a `<table>_bool_exp`: column names
6550/// mapping to comparisons, and `_and`, `_or` and `_not` for structure. It
6551/// nests, so this recurses.
6552///
6553/// Nothing is dropped silently. An operator that is not recognised widens the
6554/// result set if it is ignored -- every row for a query, every row for a
6555/// mutation -- so it is an error instead.
6556fn build_where_clause(
6557    where_value: Option<&serde_json::Value>,
6558    start_param_idx: usize,
6559    scope: &WhereScope<'_>,
6560) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6561    let mut values: Vec<serde_json::Value> = Vec::new();
6562    let mut param_idx = start_param_idx;
6563    let mut alias_counter = 0usize;
6564
6565    let condition = match where_value {
6566        Some(value) => build_condition(
6567            value,
6568            scope,
6569            &mut param_idx,
6570            &mut values,
6571            &mut alias_counter,
6572        )?,
6573        None => None,
6574    };
6575
6576    Ok(match condition {
6577        Some(sql) => (format!("WHERE {}", sql), values),
6578        None => (String::new(), values),
6579    })
6580}
6581
6582/// The table a boolean expression is being read against.
6583///
6584/// Column references are qualified with `sql_ref` so that a predicate over a
6585/// relationship -- which becomes a correlated `EXISTS` against another table --
6586/// can tell the two apart. Without the qualification a child column sharing a
6587/// parent column's name would silently resolve to whichever PostgreSQL
6588/// preferred.
6589///
6590/// `resolution` is absent where a caller has no schema cache to hand, which is
6591/// how the mutation paths are called. A relationship predicate then reports
6592/// that it cannot be resolved rather than being read as a column.
6593pub struct WhereScope<'a> {
6594    /// The table itself, for the comparisons that need a column's type.
6595    qualified: postrust_core::api_request::QualifiedIdentifier,
6596    /// How to refer to this table's columns in SQL.
6597    sql_ref: String,
6598    /// How to refer to this table's *row*, which is what a function taking the
6599    /// row is passed. A qualified name reads as a column reference there, so
6600    /// this is the bare alias.
6601    row_ref: String,
6602    /// The GraphQL type name, which is the key into the relationship map.
6603    type_name: String,
6604    /// How to refer to the columns of the table the predicate started at, and
6605    /// which table that is.
6606    ///
6607    /// A column comparison written `["$", "name"]` means that table's column,
6608    /// however many relationships the predicate has descended through since --
6609    /// which is what makes the form worth having. Equal to this table's own
6610    /// until an `EXISTS` is entered.
6611    root_ref: String,
6612    root_qualified: postrust_core::api_request::QualifiedIdentifier,
6613    /// The names this table's columns are exposed under, so a predicate
6614    /// written against a field can be written as SQL against a column.
6615    names: &'a crate::names::NameOverrides,
6616    resolution: Option<WhereResolution<'a>>,
6617    /// Who is asking, so that a predicate following a relationship narrows the
6618    /// far side to what that caller may read.
6619    ///
6620    /// Unrestricted unless a scope is told otherwise, which is what leaves
6621    /// every path that has no caller to hand -- the mutation ones -- behaving
6622    /// as it did.
6623    caller: Option<crate::role::Caller<'a>>,
6624}
6625
6626/// What a scope needs to follow a relationship.
6627struct WhereResolution<'a> {
6628    cache: &'a SchemaCache,
6629    relationships: &'a HashMap<String, Vec<RelationshipField>>,
6630}
6631
6632impl<'a> WhereScope<'a> {
6633    /// A scope over a table addressed by its qualified name.
6634    pub fn table(
6635        schema: &str,
6636        table: &str,
6637        type_name: &str,
6638        names: &'a crate::names::NameOverrides,
6639    ) -> Self {
6640        Self {
6641            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6642            sql_ref: format!(
6643                "{}.{}",
6644                postrust_sql::escape_ident(schema),
6645                postrust_sql::escape_ident(table)
6646            ),
6647            row_ref: postrust_sql::escape_ident(table),
6648            type_name: type_name.to_string(),
6649            root_ref: format!(
6650                "{}.{}",
6651                postrust_sql::escape_ident(schema),
6652                postrust_sql::escape_ident(table)
6653            ),
6654            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6655            names,
6656            resolution: None,
6657            caller: None,
6658        }
6659    }
6660
6661    /// The same, referred to by an alias rather than by its name.
6662    ///
6663    /// A statement that writes a table has to name that table's row in
6664    /// `RETURNING`, and a bare table name there is read as a *column* first --
6665    /// so `area.area`, a geography column in a table of the same name, made
6666    /// `to_jsonb("area")` the shape rather than the row. The alias is a name
6667    /// no column has.
6668    fn under_alias(mut self, alias: &str) -> Self {
6669        // The root moves with it: this is the outermost table being renamed,
6670        // not a step into another one.
6671        if self.root_ref == self.sql_ref {
6672            self.root_ref = postrust_sql::escape_ident(alias);
6673        }
6674        self.sql_ref = postrust_sql::escape_ident(alias);
6675        self.row_ref = postrust_sql::escape_ident(alias);
6676        self
6677    }
6678
6679    /// The same, able to follow relationships.
6680    fn with_resolution(
6681        mut self,
6682        cache: &'a SchemaCache,
6683        relationships: &'a HashMap<String, Vec<RelationshipField>>,
6684    ) -> Self {
6685        self.resolution = Some(WhereResolution {
6686            cache,
6687            relationships,
6688        });
6689        self
6690    }
6691
6692    /// A scope over an aliased table, able to follow relationships.
6693    #[allow(clippy::too_many_arguments)]
6694    fn for_alias(
6695        schema: &str,
6696        table: &str,
6697        alias: &str,
6698        type_name: &str,
6699        cache: &'a SchemaCache,
6700        relationships: &'a HashMap<String, Vec<RelationshipField>>,
6701        names: &'a crate::names::NameOverrides,
6702    ) -> Self {
6703        Self {
6704            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6705            sql_ref: postrust_sql::escape_ident(alias),
6706            row_ref: postrust_sql::escape_ident(alias),
6707            type_name: type_name.to_string(),
6708            root_ref: postrust_sql::escape_ident(alias),
6709            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6710            names,
6711            resolution: Some(WhereResolution {
6712                cache,
6713                relationships,
6714            }),
6715            caller: None,
6716        }
6717    }
6718
6719    /// A scope over an aliased table, for the inside of an `EXISTS`.
6720    /// A scope over the *other* table of a relationship, under an alias.
6721    ///
6722    /// The child's own qualified name, not the parent's: a comparison inside
6723    /// an `EXISTS` is against the child's columns, so it is the child's types
6724    /// and the child's renames that decide how to write it.
6725    fn aliased(
6726        schema: &str,
6727        table: &str,
6728        alias: &str,
6729        type_name: &str,
6730        from: &WhereScope<'a>,
6731    ) -> Self {
6732        Self {
6733            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6734            sql_ref: postrust_sql::escape_ident(alias),
6735            row_ref: postrust_sql::escape_ident(alias),
6736            type_name: type_name.to_string(),
6737            // Carried, not replaced: `$` names the table the predicate began
6738            // at, which is the whole point of descending with it.
6739            root_ref: from.root_ref.clone(),
6740            root_qualified: from.root_qualified.clone(),
6741            names: from.names,
6742            resolution: from.resolution.as_ref().map(|r| WhereResolution {
6743                cache: r.cache,
6744                relationships: r.relationships,
6745            }),
6746            // Carried across the relationship: the caller does not change on
6747            // the way into an EXISTS, and the far side has its own permission
6748            // to be narrowed by.
6749            caller: from.caller,
6750        }
6751    }
6752
6753    /// Narrow what this scope's predicates may reach to one caller.
6754    pub fn for_caller(mut self, caller: crate::role::Caller<'a>) -> Self {
6755        self.caller = Some(caller);
6756        self
6757    }
6758
6759    /// The same scope with no caller: what a permission's own predicate is
6760    /// evaluated as. See `PERMISSION`.
6761    fn as_system(&self) -> WhereScope<'a> {
6762        WhereScope {
6763            qualified: self.qualified.clone(),
6764            sql_ref: self.sql_ref.clone(),
6765            row_ref: self.row_ref.clone(),
6766            type_name: self.type_name.clone(),
6767            // The root moves here. `$` in a permission names the table the
6768            // permission is *written on*, which is this one -- not whatever
6769            // table the query happened to start at. Reading `dml_rbp_tasks`
6770            // through a project's `where: {tasks: {...}}` compared the task's
6771            // grant against a column of `dml_rbp_projects` until this, and
6772            // answered with rows the role may not see.
6773            root_ref: self.sql_ref.clone(),
6774            root_qualified: self.qualified.clone(),
6775            names: self.names,
6776            resolution: self.resolution.as_ref().map(|r| WhereResolution {
6777                cache: r.cache,
6778                relationships: r.relationships,
6779            }),
6780            caller: None,
6781        }
6782    }
6783
6784    /// What a field name refers to, as SQL: a column of this table, or the
6785    /// call that produces a computed field.
6786    ///
6787    /// A computed field is in the boolean expression because it is on the type
6788    /// -- `where: {sum_float_offset: {_gt: 5}}` is a question a client can
6789    /// write -- and reading it as a column answered `column
6790    /// float_test.sum_float_offset does not exist`.
6791    fn column(&self, name: &str) -> String {
6792        let column = self.column_name(name);
6793        let plain = format!("{}.{}", self.sql_ref, postrust_sql::escape_ident(column));
6794        let Some(cache) = self.resolution.as_ref().map(|r| r.cache) else {
6795            return plain;
6796        };
6797        let Some(table) = cache.get_table(&self.qualified) else {
6798            return plain;
6799        };
6800        if table.get_column(column).is_some() {
6801            return plain;
6802        }
6803        let function = self
6804            .names
6805            .computed_source(&table.schema, &table.name, name)
6806            .unwrap_or(name);
6807        match table.get_computed_column(function) {
6808            Some(definition) => {
6809                computed_call(definition, &format!("{}.*", self.row_ref), &self.row_ref)
6810            }
6811            None => plain,
6812        }
6813    }
6814
6815    /// The column behind a field name.
6816    fn column_name<'n>(&'n self, name: &'n str) -> &'n str {
6817        self.names
6818            .column_source(&self.qualified.schema, &self.qualified.name, name)
6819            .unwrap_or(name)
6820    }
6821
6822    /// The PostgreSQL type of one of this table's columns, where it can be
6823    /// found. A spatial comparison needs it: the same function takes a
6824    /// geometry or a geography and the operand has to be cast to match.
6825    fn column_type(&self, name: &str) -> Option<String> {
6826        let cache = self.resolution.as_ref()?.cache;
6827        let table = cache.get_table(&self.qualified)?;
6828        table
6829            .get_column(self.column_name(name))
6830            .map(|c| c.nominal_type.clone())
6831    }
6832
6833    /// The other side of a column-to-column comparison, as SQL.
6834    ///
6835    /// Hasura writes the operand two ways. A bare name is a column of *this*
6836    /// table -- `{"bid_price": {"$cgt": "price"}}` compares two columns of one
6837    /// auction. `["$", "name"]` is a column of the table the permission is
6838    /// written on, which is what makes the form useful inside a predicate over
6839    /// a relationship: `{"item": {"item_quantity": {"_cgte": ["$",
6840    /// "order_cart_quantity"]}}}` asks whether the item has as many in stock
6841    /// as the cart is asking for, and the cart is the outer row.
6842    ///
6843    /// Nothing else is accepted. A longer path would walk relationships to
6844    /// reach the column, which Hasura allows and no case here writes -- and
6845    /// reading it wrongly would widen a permission rather than narrow it.
6846    fn other_column(
6847        &self,
6848        operand: &serde_json::Value,
6849        op: &str,
6850    ) -> Result<String, async_graphql::Error> {
6851        use serde_json::Value;
6852
6853        let refused = |what: &str| {
6854            async_graphql::Error::new(format!(
6855                "the \"{}\" comparison takes the name of a column, {}",
6856                op, what
6857            ))
6858        };
6859        let (against, qualified, name) = match operand {
6860            Value::String(name) => (&self.sql_ref, &self.qualified, name.as_str()),
6861            Value::Array(path) => match path.as_slice() {
6862                [Value::String(name)] => (&self.sql_ref, &self.qualified, name.as_str()),
6863                [Value::String(root), Value::String(name)] if root == "$" => {
6864                    (&self.root_ref, &self.root_qualified, name.as_str())
6865                }
6866                _ => return Err(refused("or [\"$\", \"a column\"]")),
6867            },
6868            _ => return Err(refused("written as a string")),
6869        };
6870        // Read against whichever table it belongs to, since a rename is
6871        // per-table and the two are not the same table.
6872        let column = self
6873            .names
6874            .column_source(&qualified.schema, &qualified.name, name)
6875            .unwrap_or(name);
6876        Ok(format!(
6877            "{}.{}",
6878            against,
6879            postrust_sql::escape_ident(column)
6880        ))
6881    }
6882
6883    /// The relationship a predicate is following, where there is one.
6884    ///
6885    /// Looked up in the schema's own map first, which is the answer for every
6886    /// relationship the role can read. A permission may name one it cannot:
6887    /// role `Artist` reads `Track` and filters it by `{"Artist": {"id": ...}}`
6888    /// while having no permission on `Artist` at all, so that relationship is
6889    /// in no schema this role is served. The catalogue still has it -- the
6890    /// cache a scope resolves against is the unreduced one -- and the
6891    /// relationship is derived from it there.
6892    ///
6893    /// Deriving it cannot let a client reach a hidden table: a key that is not
6894    /// a field of the boolean expression is refused before any of this runs,
6895    /// and the only writer of such a key is a permission.
6896    fn relationship(&self, name: &str) -> Option<std::borrow::Cow<'a, RelationshipField>> {
6897        let resolution = self.resolution.as_ref()?;
6898        let published = resolution
6899            .relationships
6900            .get(&self.type_name)
6901            .and_then(|fields| fields.iter().find(|r| r.name == name));
6902        if let Some(found) = published {
6903            return Some(std::borrow::Cow::Borrowed(found));
6904        }
6905
6906        let table = resolution.cache.get_table(&self.qualified)?;
6907
6908        // A relationship metadata declares, which may be a mapped join the
6909        // catalogue has never heard of. `message.members` is one, and a
6910        // permission on `message` is written through it -- so this is not a
6911        // convenience: without it that filter cannot be compiled at all and
6912        // the read is refused rather than narrowed.
6913        if let Some(declared) = self
6914            .names
6915            .declared_relationships(&table.schema, &table.name)
6916        {
6917            if let Some(entry) = declared.iter().find(|entry| entry.name == name) {
6918                // Reached by a mapping, which the catalogue has never heard
6919                // of.
6920                let target = entry
6921                    .target(&table.schema)
6922                    .map(|(_, target)| target)
6923                    .unwrap_or_default();
6924                if let Some(field) = crate::schema::mapped_relationship_field(
6925                    entry,
6926                    table,
6927                    resolution.cache,
6928                    &table.schema,
6929                    &target,
6930                    &target,
6931                    &target,
6932                ) {
6933                    return Some(std::borrow::Cow::Owned(field));
6934                }
6935                // Or by a key, under the name the declaration gives it --
6936                // which is the only place that name is written down now that
6937                // a declaration carries it.
6938                if let Some(key) = entry.using.as_deref() {
6939                    if let Some(found) = resolution
6940                        .cache
6941                        .get_relationships(&self.qualified, &table.schema)
6942                        .into_iter()
6943                        .flatten()
6944                        .find(|rel| {
6945                            crate::schema::relationship_keys(rel)
6946                                .iter()
6947                                .any(|found| found == key)
6948                        })
6949                    {
6950                        return Some(std::borrow::Cow::Owned(
6951                            crate::schema::relationship::RelationshipField::from_relationship_named(
6952                                found,
6953                                &found.foreign_table().name,
6954                                Some(&entry.name),
6955                            ),
6956                        ));
6957                    }
6958                }
6959            }
6960        }
6961
6962        resolution
6963            .cache
6964            .get_relationships(&self.qualified, &table.schema)?
6965            .iter()
6966            .find_map(|rel| {
6967                let given = crate::schema::relationship_keys(rel)
6968                    .iter()
6969                    .find_map(|key| self.names.relationship(&table.schema, &table.name, key));
6970                // The target's own name stands in for its GraphQL type, which
6971                // this role has none of. Nothing reads it but the lookup
6972                // above, and that lookup will land here again.
6973                let field = RelationshipField::from_relationship_named(
6974                    rel,
6975                    &rel.foreign_table().name,
6976                    given,
6977                );
6978                (field.name == name).then_some(std::borrow::Cow::Owned(field))
6979            })
6980    }
6981}
6982
6983/// The SQL operator behind a column-to-column comparison, where this is one.
6984///
6985/// `_ceq`, `_cne`, `_cgt`, `_cgte`, `_clt`, `_clte`: the `c` is for column.
6986/// Only a permission writes these -- no boolean expression a client can write
6987/// has them, because a client has no way to name the row a comparison would be
6988/// against. A permission does: `{"bid_price": {"$cgt": "price"}}` on `auction`
6989/// grants the rows whose bid has passed the reserve, which is a fact about the
6990/// row rather than about the caller.
6991fn column_comparison(op: &str) -> Option<&'static str> {
6992    Some(match op {
6993        "_ceq" => "=",
6994        "_cne" => "<>",
6995        "_cgt" => ">",
6996        "_cgte" => ">=",
6997        "_clt" => "<",
6998        "_clte" => "<=",
6999        _ => return None,
7000    })
7001}
7002
7003/// One boolean expression, as SQL. `None` where it constrains nothing.
7004fn build_condition(
7005    value: &serde_json::Value,
7006    scope: &WhereScope<'_>,
7007    param_idx: &mut usize,
7008    values: &mut Vec<serde_json::Value>,
7009    alias_counter: &mut usize,
7010) -> Result<Option<String>, async_graphql::Error> {
7011    let serde_json::Value::Object(map) = value else {
7012        return Ok(None);
7013    };
7014
7015    let mut conditions: Vec<String> = Vec::new();
7016
7017    for (key, val) in map {
7018        match key.as_str() {
7019            // An empty `_and` is true and an empty `_or` is false, which is
7020            // what SQL's own identities give: nothing to AND constrains
7021            // nothing, nothing to OR matches nothing.
7022            "_and" | "_or" => {
7023                let members = val.as_array().ok_or_else(|| {
7024                    async_graphql::Error::new(format!("\"{}\" takes a list of expressions", key))
7025                })?;
7026                let mut parts = Vec::new();
7027                for member in members {
7028                    if let Some(sql) =
7029                        build_condition(member, scope, param_idx, values, alias_counter)?
7030                    {
7031                        parts.push(sql);
7032                    }
7033                }
7034                if parts.is_empty() {
7035                    if key == "_or" {
7036                        conditions.push("false".to_string());
7037                    }
7038                    continue;
7039                }
7040                let joiner = if key == "_and" { " AND " } else { " OR " };
7041                conditions.push(format!("({})", parts.join(joiner)));
7042            }
7043            "_not" => {
7044                if let Some(sql) = build_condition(val, scope, param_idx, values, alias_counter)? {
7045                    conditions.push(format!("NOT ({})", sql));
7046                }
7047            }
7048            // A permission's own predicate, which is the server's rather
7049            // than the caller's. See `PERMISSION`.
7050            PERMISSION => {
7051                if let Some(sql) =
7052                    build_condition(val, &scope.as_system(), param_idx, values, alias_counter)?
7053                {
7054                    conditions.push(sql);
7055                }
7056            }
7057            // A question about a table nothing relates this one to. Only a
7058            // permission writes it; no client can, because no boolean
7059            // expression has the field.
7060            "_exists" => {
7061                conditions.push(table_exists_sql(
7062                    val,
7063                    scope,
7064                    param_idx,
7065                    values,
7066                    alias_counter,
7067                )?);
7068            }
7069            // A relationship is filtered by filtering the rows at its other
7070            // end. `where: {articles: {title: {_eq: "x"}}}` keeps the authors
7071            // that have such an article -- an `EXISTS` correlated back to the
7072            // parent row, which is also what it means for a to-one
7073            // relationship, where the subquery simply cannot match twice.
7074            name if scope.relationship(name).is_some() => {
7075                let relationship = scope.relationship(name).expect("just checked");
7076                conditions.push(exists_sql(
7077                    &relationship,
7078                    val,
7079                    scope,
7080                    param_idx,
7081                    values,
7082                    alias_counter,
7083                )?);
7084            }
7085            // A question about the whole related set rather than about any
7086            // one row of it. `where: {articles_aggregate: {count: {predicate:
7087            // {_gt: 2}}}}` keeps the authors with more than two articles,
7088            // which no `EXISTS` over one article can say.
7089            name if scope.column_type(name).is_none()
7090                && name
7091                    .strip_suffix("_aggregate")
7092                    .and_then(|rel| scope.relationship(rel))
7093                    .is_some_and(|rel| rel.is_list) =>
7094            {
7095                let relationship = name
7096                    .strip_suffix("_aggregate")
7097                    .and_then(|rel| scope.relationship(rel))
7098                    .expect("just checked");
7099                conditions.push(aggregate_predicate_sql(
7100                    &relationship,
7101                    val,
7102                    scope,
7103                    param_idx,
7104                    values,
7105                    alias_counter,
7106                )?);
7107            }
7108            column => {
7109                let quoted = scope.column(column);
7110                match val {
7111                    serde_json::Value::Object(ops) => {
7112                        let column_type = scope.column_type(column);
7113                        for (op, operand) in ops {
7114                            // A comparison against another column rather than
7115                            // against a value, which only a permission can
7116                            // write. It needs the scope to say *which* column,
7117                            // so it is answered here rather than in
7118                            // `comparison_sql`, which sees one side.
7119                            if let Some(other) = column_comparison(op) {
7120                                conditions.push(format!(
7121                                    "{} {} {}",
7122                                    quoted,
7123                                    other,
7124                                    scope.other_column(operand, op)?
7125                                ));
7126                                continue;
7127                            }
7128                            conditions.push(comparison_sql(
7129                                &quoted,
7130                                column,
7131                                column_type.as_deref(),
7132                                op,
7133                                operand,
7134                                param_idx,
7135                                values,
7136                            )?);
7137                        }
7138                    }
7139                    // `{id: 1}` rather than `{id: {_eq: 1}}`. Not a spelling
7140                    // Hasura accepts, but one that costs nothing to read and
7141                    // that hand-written queries reach for.
7142                    other => {
7143                        conditions.push(format!("{} = ${}", quoted, param_idx));
7144                        values.push(other.clone());
7145                        *param_idx += 1;
7146                    }
7147                }
7148            }
7149        }
7150    }
7151
7152    Ok(if conditions.is_empty() {
7153        None
7154    } else if conditions.len() == 1 {
7155        conditions.pop()
7156    } else {
7157        Some(conditions.join(" AND "))
7158    })
7159}
7160
7161/// A predicate over a relationship, as a correlated `EXISTS`.
7162///
7163/// Only a plain key join is expressible this way. A many-to-many through a
7164/// junction and a computed relationship both need more than a pair of columns
7165/// to correlate on, and saying so is the only honest answer -- reading the
7166/// predicate as though it constrained nothing would return every parent row.
7167fn exists_sql(
7168    relationship: &RelationshipField,
7169    child_expression: &serde_json::Value,
7170    scope: &WhereScope<'_>,
7171    param_idx: &mut usize,
7172    values: &mut Vec<serde_json::Value>,
7173    alias_counter: &mut usize,
7174) -> Result<String, async_graphql::Error> {
7175    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7176        async_graphql::Error::new(format!(
7177            "filtering on the relationship \"{}\" is not available here",
7178            relationship.name
7179        ))
7180    })?;
7181
7182    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7183        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7184
7185    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7186        return Err(async_graphql::Error::new(format!(
7187            "filtering on \"{}\" is not supported: it is reached through a junction \
7188             rather than by a key",
7189            relationship.name
7190        )));
7191    }
7192
7193    *alias_counter += 1;
7194    let alias = format!("pgrst_rel_{}", alias_counter);
7195    let child_scope = WhereScope::aliased(
7196        &plan.foreign_schema,
7197        &plan.foreign_table,
7198        &alias,
7199        &relationship.target_type,
7200        scope,
7201    );
7202
7203    // A computed relationship is correlated by argument -- the function takes
7204    // the parent row -- where a key relationship is correlated by columns.
7205    let (source, mut correlation) = match &plan.function {
7206        Some(function) => (
7207            format!(
7208                "{}.{}({})",
7209                postrust_sql::escape_ident(&function.schema),
7210                postrust_sql::escape_ident(&function.name),
7211                scope.row_ref
7212            ),
7213            Vec::new(),
7214        ),
7215        None => {
7216            let mut columns = Vec::with_capacity(plan.columns.len());
7217            for (parent_column, child_column) in &plan.columns {
7218                columns.push(format!(
7219                    "{} = {}",
7220                    scope.column(parent_column),
7221                    child_scope.column(child_column)
7222                ));
7223            }
7224            (
7225                format!(
7226                    "{}.{}",
7227                    postrust_sql::escape_ident(&plan.foreign_schema),
7228                    postrust_sql::escape_ident(&plan.foreign_table)
7229                ),
7230                columns,
7231            )
7232        }
7233    };
7234
7235    let child_condition = build_condition(
7236        child_expression,
7237        &child_scope,
7238        param_idx,
7239        values,
7240        alias_counter,
7241    )?;
7242    if let Some(sql) = child_condition {
7243        correlation.push(format!("({})", sql));
7244    }
7245
7246    // The far side's own permission. `where: {author: {name: {_eq: "Ann"}}}`
7247    // asks whether a related row exists, and a row this caller may not read is
7248    // one it may not learn the existence of either -- so the child's filter
7249    // narrows the EXISTS exactly as it narrows a read of the same table.
7250    if let Some(caller) = &child_scope.caller {
7251        // Through `permission_predicate` rather than `role::read_predicate`,
7252        // so the far side's filter is marked as the server's own -- see
7253        // `PERMISSION`. Without the mark it was compiled as the caller, and a
7254        // filter that follows a relationship of its own then needed a
7255        // permission on *that* table: `dml_rbp_tasks` is filtered through
7256        // `grants`, which the role reading tasks may not read.
7257        let permission = permission_predicate(
7258            caller,
7259            child_scope.names,
7260            &plan.foreign_schema,
7261            &plan.foreign_table,
7262        )?;
7263        if let Some(predicate) = permission {
7264            if let Some(sql) =
7265                build_condition(&predicate, &child_scope, param_idx, values, alias_counter)?
7266            {
7267                correlation.push(format!("({})", sql));
7268            }
7269        }
7270    }
7271    if correlation.is_empty() {
7272        correlation.push("true".to_string());
7273    }
7274
7275    Ok(format!(
7276        "EXISTS (SELECT 1 FROM {} AS {} WHERE {})",
7277        source,
7278        postrust_sql::escape_ident(&alias),
7279        correlation.join(" AND ")
7280    ))
7281}
7282
7283/// `_exists`, the predicate only a permission can write, as SQL.
7284///
7285/// ```json
7286/// {"_exists": {"_table": "user", "_where": {"id": "X-Hasura-User-Id",
7287///                                           "is_admin": true}}}
7288/// ```
7289///
7290/// It asks whether a row exists in *another* table -- one no foreign key
7291/// relates to this one. That is how Hasura writes "the caller is an
7292/// administrator" without asking the caller to say so: the row that decides it
7293/// lives in a table of its own, and whether the account this predicate guards
7294/// is readable does not depend on which account it is.
7295///
7296/// So the subselect is **uncorrelated**, which is the whole difference from
7297/// the `EXISTS` a relationship predicate builds. There are no key columns to
7298/// join on -- if there were, the permission would have been written as a
7299/// relationship -- and the `_where` is read against the named table's columns
7300/// alone.
7301///
7302/// The caller's own permissions are not applied inside it, and that is not an
7303/// oversight. The point of `_exists` is to consult a table the role has no
7304/// access to: in Hasura's corpus the role that reads `account` through this
7305/// predicate may not read `public.user` at all. Narrowing the subselect to
7306/// what the caller may read would make the predicate refuse itself.
7307fn table_exists_sql(
7308    spec: &serde_json::Value,
7309    scope: &WhereScope<'_>,
7310    param_idx: &mut usize,
7311    values: &mut Vec<serde_json::Value>,
7312    alias_counter: &mut usize,
7313) -> Result<String, async_graphql::Error> {
7314    let Some((schema, table)) = crate::role::exists_target(spec, &scope.qualified.schema) else {
7315        return Err(async_graphql::Error::new(
7316            "\"_exists\" needs a \"_table\" to look in",
7317        ));
7318    };
7319
7320    // The name comes from a permission document rather than from a request,
7321    // but it still reaches SQL, and a table that is not there would reach it as
7322    // a syntax error at the far end rather than as an answer here.
7323    let qualified = postrust_core::api_request::QualifiedIdentifier::new(&schema, &table);
7324    if scope
7325        .resolution
7326        .as_ref()
7327        .is_some_and(|r| r.cache.get_table(&qualified).is_none())
7328    {
7329        return Err(async_graphql::Error::new(format!(
7330            "\"_exists\" names a table this server does not have: \"{}.{}\"",
7331            schema, table
7332        )));
7333    }
7334
7335    *alias_counter += 1;
7336    let alias = format!("pgrst_exists_{}", alias_counter);
7337    // The type this table is exposed as, which is the key into the
7338    // relationship map -- so a `_where` may follow the named table's own
7339    // relationships, the way any other predicate over it could.
7340    let type_name = scope
7341        .names
7342        .base_name(&schema, &table)
7343        .unwrap_or(&table)
7344        .to_string();
7345    let mut inner = WhereScope::aliased(&schema, &table, &alias, &type_name, scope);
7346    // See above: the predicate is the permission, so it is not itself
7347    // subject to one.
7348    inner.caller = None;
7349
7350    let condition = match spec.get("_where") {
7351        Some(predicate) => build_condition(predicate, &inner, param_idx, values, alias_counter)?,
7352        None => None,
7353    };
7354
7355    Ok(format!(
7356        "EXISTS (SELECT 1 FROM {}.{} AS {} WHERE {})",
7357        postrust_sql::escape_ident(&schema),
7358        postrust_sql::escape_ident(&table),
7359        postrust_sql::escape_ident(&alias),
7360        condition.unwrap_or_else(|| "true".to_string())
7361    ))
7362}
7363
7364/// A predicate over an aggregate of a related set, as SQL.
7365///
7366/// `{count: {predicate: {_gt: 2}, filter: {...}}}` becomes a scalar subselect
7367/// correlated back to the parent row, compared the way any column is:
7368///
7369/// ```text
7370/// (SELECT count(*) FROM "public"."article" AS pgrst_rel_1
7371///   WHERE "author"."id" = pgrst_rel_1."author_id" AND (...)) > $1
7372/// ```
7373///
7374/// A scalar subselect rather than an `EXISTS`, because the answer is a number
7375/// or a truth value and not whether a row is there -- and because over no rows
7376/// at all `count` is zero and `bool_and` is null, which is what SQL says and
7377/// what a client asking "no articles" is relying on.
7378fn aggregate_predicate_sql(
7379    relationship: &RelationshipField,
7380    expression: &serde_json::Value,
7381    scope: &WhereScope<'_>,
7382    param_idx: &mut usize,
7383    values: &mut Vec<serde_json::Value>,
7384    alias_counter: &mut usize,
7385) -> Result<String, async_graphql::Error> {
7386    let serde_json::Value::Object(over) = expression else {
7387        return Ok("true".to_string());
7388    };
7389
7390    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7391        async_graphql::Error::new(format!(
7392            "filtering on the relationship \"{}\" is not available here",
7393            relationship.name
7394        ))
7395    })?;
7396    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7397        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7398    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7399        return Err(async_graphql::Error::new(format!(
7400            "aggregating over \"{}\" is not supported: it is reached through a junction \
7401             rather than by a key",
7402            relationship.name
7403        )));
7404    }
7405
7406    let mut conditions: Vec<String> = Vec::new();
7407    for (function, spec) in over {
7408        if spec.is_null() {
7409            continue;
7410        }
7411        let serde_json::Value::Object(spec) = spec else {
7412            continue;
7413        };
7414        let Some(predicate) = spec.get("predicate").filter(|p| !p.is_null()) else {
7415            return Err(validation_error(format!(
7416                "\"{}\" over \"{}\" needs a predicate",
7417                function, relationship.name
7418            )));
7419        };
7420
7421        *alias_counter += 1;
7422        let alias = format!("pgrst_agg_{}", alias_counter);
7423        let child_scope = WhereScope::aliased(
7424            &plan.foreign_schema,
7425            &plan.foreign_table,
7426            &alias,
7427            &relationship.target_type,
7428            scope,
7429        );
7430
7431        // Correlated the way an `EXISTS` over the same relationship is: by
7432        // the key, or by handing the parent row to the function.
7433        let (source, mut correlation) = match &plan.function {
7434            Some(routine) => (
7435                format!(
7436                    "{}.{}({})",
7437                    postrust_sql::escape_ident(&routine.schema),
7438                    postrust_sql::escape_ident(&routine.name),
7439                    scope.row_ref
7440                ),
7441                Vec::new(),
7442            ),
7443            None => {
7444                let mut columns = Vec::with_capacity(plan.columns.len());
7445                for (parent_column, child_column) in &plan.columns {
7446                    columns.push(format!(
7447                        "{} = {}",
7448                        scope.column(parent_column),
7449                        child_scope.column(child_column)
7450                    ));
7451                }
7452                (
7453                    format!(
7454                        "{}.{}",
7455                        postrust_sql::escape_ident(&plan.foreign_schema),
7456                        postrust_sql::escape_ident(&plan.foreign_table)
7457                    ),
7458                    columns,
7459                )
7460            }
7461        };
7462        // `filter` narrows what is aggregated, and is an ordinary boolean
7463        // expression over the child.
7464        if let Some(filter) = spec.get("filter").filter(|f| !f.is_null()) {
7465            if let Some(sql) =
7466                build_condition(filter, &child_scope, param_idx, values, alias_counter)?
7467            {
7468                correlation.push(format!("({})", sql));
7469            }
7470        }
7471        if correlation.is_empty() {
7472            correlation.push("true".to_string());
7473        }
7474
7475        let arguments = spec.get("arguments").filter(|a| !a.is_null());
7476        let distinct = matches!(spec.get("distinct"), Some(serde_json::Value::Bool(true)));
7477        let (aggregate, operand_type) = match function.as_str() {
7478            "count" => {
7479                let columns: Vec<String> = match arguments {
7480                    Some(serde_json::Value::Array(items)) => items
7481                        .iter()
7482                        .filter_map(|item| item.as_str())
7483                        .map(|column| child_scope.column(column))
7484                        .collect(),
7485                    Some(serde_json::Value::String(one)) => vec![child_scope.column(one)],
7486                    _ => Vec::new(),
7487                };
7488                let counted = match columns.len() {
7489                    0 => "*".to_string(),
7490                    1 => columns.into_iter().next().unwrap_or_default(),
7491                    _ => format!("({})", columns.join(", ")),
7492                };
7493                let counted = match distinct && counted != "*" {
7494                    true => format!("DISTINCT {}", counted),
7495                    false => counted,
7496                };
7497                (format!("count({})", counted), "integer")
7498            }
7499            "bool_and" | "bool_or" => {
7500                let Some(column) = arguments.and_then(|a| a.as_str()) else {
7501                    return Err(validation_error(format!(
7502                        "\"{}\" over \"{}\" needs the column to fold",
7503                        function, relationship.name
7504                    )));
7505                };
7506                (
7507                    format!("{}({})", function, child_scope.column(column)),
7508                    "boolean",
7509                )
7510            }
7511            other => {
7512                return Err(validation_error(format!(
7513                    "\"{}\" is not an aggregate a predicate can be written over",
7514                    other
7515                )))
7516            }
7517        };
7518
7519        let subselect = format!(
7520            "(SELECT {} FROM {} AS {} WHERE {})",
7521            aggregate,
7522            source,
7523            postrust_sql::escape_ident(&alias),
7524            correlation.join(" AND ")
7525        );
7526        let serde_json::Value::Object(operators) = predicate else {
7527            return Err(validation_error(format!(
7528                "the predicate on \"{}\" is not a comparison",
7529                function
7530            )));
7531        };
7532        for (operator, operand) in operators {
7533            conditions.push(comparison_sql(
7534                &subselect,
7535                function,
7536                Some(operand_type),
7537                operator,
7538                operand,
7539                param_idx,
7540                values,
7541            )?);
7542        }
7543    }
7544
7545    Ok(match conditions.is_empty() {
7546        true => "true".to_string(),
7547        false => format!("({})", conditions.join(" AND ")),
7548    })
7549}
7550
7551/// One spatial comparison, as a PostGIS call.
7552///
7553/// The operand arrives as GeoJSON, which is what Hasura accepts and what a
7554/// client sends, so it is parsed by `ST_GeomFromGeoJSON` rather than bound as
7555/// a shape. The cast that follows is the column's own type: the same function
7556/// takes a geometry or a geography and picking the wrong one is not an
7557/// overload PostGIS has.
7558#[allow(clippy::too_many_arguments)]
7559fn postgis_sql(
7560    quoted: &str,
7561    column_type: Option<&str>,
7562    function: &str,
7563    op: &str,
7564    operand: &serde_json::Value,
7565    param_idx: &mut usize,
7566    values: &mut Vec<serde_json::Value>,
7567) -> Result<String, async_graphql::Error> {
7568    let is_geography = column_type == Some("geography");
7569    let mut shape = |value: &serde_json::Value, param_idx: &mut usize| -> String {
7570        let placeholder = format!("${}", param_idx);
7571        *param_idx += 1;
7572        values.push(value.clone());
7573        match is_geography {
7574            true => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
7575            false => format!("ST_GeomFromGeoJSON({})", placeholder),
7576        }
7577    };
7578
7579    match op {
7580        // `{distance, from}`: the shape and how far from it.
7581        "_st_d_within" | "_st_3d_d_within" => {
7582            let serde_json::Value::Object(spec) = operand else {
7583                return Err(async_graphql::Error::new(format!(
7584                    "\"{}\" takes {{distance, from}}",
7585                    op
7586                )));
7587            };
7588            let Some(from) = spec.get("from") else {
7589                return Err(async_graphql::Error::new(format!(
7590                    "\"{}\" needs a shape to measure from",
7591                    op
7592                )));
7593            };
7594            let Some(distance) = spec.get("distance") else {
7595                return Err(async_graphql::Error::new(format!(
7596                    "\"{}\" needs a distance",
7597                    op
7598                )));
7599            };
7600            let from_sql = shape(from, param_idx);
7601            let distance_sql = format!("${}", param_idx);
7602            *param_idx += 1;
7603            values.push(distance.clone());
7604            // On a sphere the answer depends on which sphere: `ST_DWithin`
7605            // over geography measures on the spheroid by default and on a
7606            // perfect sphere when told not to, and the two disagree by enough
7607            // to change which rows come back. Geometry has no such argument.
7608            let spheroid = match (is_geography, spec.get("use_spheroid")) {
7609                (true, Some(use_spheroid)) if !use_spheroid.is_null() => {
7610                    let placeholder = format!("${}", param_idx);
7611                    *param_idx += 1;
7612                    values.push(use_spheroid.clone());
7613                    format!(", {}::boolean", placeholder)
7614                }
7615                _ => String::new(),
7616            };
7617            Ok(format!(
7618                "{}({}, {}, {}::float8{})",
7619                function, quoted, from_sql, distance_sql, spheroid
7620            ))
7621        }
7622        // A raster against another raster, bound as one rather than parsed.
7623        "_st_intersects_rast" => {
7624            let placeholder = format!("${}", param_idx);
7625            *param_idx += 1;
7626            values.push(operand.clone());
7627            Ok(format!("{}({}, {}::raster)", function, quoted, placeholder))
7628        }
7629        // A raster against a shape, in one band or in any.
7630        "_st_intersects_geom_nband" | "_st_intersects_nband_geom" => {
7631            let serde_json::Value::Object(spec) = operand else {
7632                return Err(async_graphql::Error::new(format!(
7633                    "\"{}\" takes an object",
7634                    op
7635                )));
7636            };
7637            let Some(geometry) = spec.get("geommin") else {
7638                return Err(async_graphql::Error::new(format!(
7639                    "\"{}\" needs a shape",
7640                    op
7641                )));
7642            };
7643            let band = spec.get("nband").filter(|v| !v.is_null());
7644            let geometry_sql = shape(geometry, param_idx);
7645            Ok(match band {
7646                None => format!("{}({}, {})", function, quoted, geometry_sql),
7647                Some(band) => {
7648                    let placeholder = format!("${}", param_idx);
7649                    *param_idx += 1;
7650                    values.push(band.clone());
7651                    // `ST_Intersects(raster, nband, geometry)` is the spelling
7652                    // with a band; the argument order is PostGIS's, not the
7653                    // input's.
7654                    format!(
7655                        "{}({}, {}::int, {})",
7656                        function, quoted, placeholder, geometry_sql
7657                    )
7658                }
7659            })
7660        }
7661        // Everything else is the relation between this shape and one other.
7662        _ => {
7663            let other = shape(operand, param_idx);
7664            Ok(format!("{}({}, {})", function, quoted, other))
7665        }
7666    }
7667}
7668
7669/// A list of values as an array PostgreSQL will read.
7670///
7671/// Bound one element at a time rather than as one parameter. A JSON array
7672/// arrives as the text `["a","b"]`, which is not an array literal -- PostgreSQL
7673/// answers `malformed array literal` and means it.
7674fn sql_array(
7675    items: &[serde_json::Value],
7676    cast: &str,
7677    param_idx: &mut usize,
7678    values: &mut Vec<serde_json::Value>,
7679) -> String {
7680    if items.is_empty() {
7681        return format!("ARRAY[]::{}", cast);
7682    }
7683    let placeholders: Vec<String> = items
7684        .iter()
7685        .map(|item| {
7686            let placeholder = format!("${}", param_idx);
7687            *param_idx += 1;
7688            values.push(item.clone());
7689            placeholder
7690        })
7691        .collect();
7692    format!("ARRAY[{}]::{}", placeholders.join(", "), cast)
7693}
7694
7695/// One comparison against one column.
7696#[allow(clippy::too_many_arguments)]
7697fn comparison_sql(
7698    quoted: &str,
7699    column: &str,
7700    column_type: Option<&str>,
7701    op: &str,
7702    operand: &serde_json::Value,
7703    param_idx: &mut usize,
7704    values: &mut Vec<serde_json::Value>,
7705) -> Result<String, async_graphql::Error> {
7706    // `_cast` is not a comparison but a change of what is being compared: the
7707    // column becomes another type and the comparisons inside it apply to that.
7708    // A geometry and a geography answer different questions about the same
7709    // shape -- one on a plane, one on a sphere -- and this is how a client asks
7710    // for the other without the schema carrying two columns.
7711    if op == "_cast" {
7712        let serde_json::Value::Object(casts) = operand else {
7713            return Err(async_graphql::Error::new(
7714                "_cast takes an object naming the type to compare as",
7715            ));
7716        };
7717        let mut conditions = Vec::new();
7718        for (target, comparisons) in casts {
7719            // The GraphQL name of what is being compared as, and the
7720            // PostgreSQL type behind it. `String` is the one that differs:
7721            // a document compared as text is compared as `text`.
7722            let pg_type = match target.as_str() {
7723                "geometry" | "geography" => target.as_str(),
7724                "String" => "text",
7725                _ => {
7726                    return Err(async_graphql::Error::new(format!(
7727                        "cannot compare \"{}\" as \"{}\"",
7728                        column, target
7729                    )))
7730                }
7731            };
7732            let serde_json::Value::Object(ops) = comparisons else {
7733                continue;
7734            };
7735            let cast = format!("{}::{}", quoted, pg_type);
7736            for (nested_op, nested_operand) in ops {
7737                conditions.push(comparison_sql(
7738                    &cast,
7739                    column,
7740                    Some(pg_type),
7741                    nested_op,
7742                    nested_operand,
7743                    param_idx,
7744                    values,
7745                )?);
7746            }
7747        }
7748        return Ok(match conditions.len() {
7749            0 => "true".to_string(),
7750            1 => conditions.pop().expect("just counted"),
7751            _ => format!("({})", conditions.join(" AND ")),
7752        });
7753    }
7754
7755    // A spatial relation is a function of two shapes rather than an operator
7756    // between them, so it is written before the operator table is consulted.
7757    if let Some(function) = crate::input::bool_exp::postgis_function(op) {
7758        return postgis_sql(
7759            quoted,
7760            column_type,
7761            function,
7762            op,
7763            operand,
7764            param_idx,
7765            values,
7766        );
7767    }
7768
7769    // A tree comparison is an operator, but one whose operand has to be cast:
7770    // `?` is "any of these labels" for an ltree and "has this key" for a
7771    // jsonb, and PostgreSQL tells them apart by the operand's type alone.
7772    if let Some((operator, cast)) = crate::input::bool_exp::ltree_operator(op) {
7773        if cast.ends_with("[]") {
7774            let items = operand.as_array().ok_or_else(|| {
7775                async_graphql::Error::new(format!(
7776                    "the \"{}\" comparison on \"{}\" takes a list",
7777                    op, column
7778                ))
7779            })?;
7780            return Ok(format!(
7781                "{} {} {}",
7782                quoted,
7783                operator,
7784                sql_array(items, cast, param_idx, values)
7785            ));
7786        }
7787        let placeholder = format!("${}", param_idx);
7788        *param_idx += 1;
7789        values.push(operand.clone());
7790        return Ok(format!("{} {} {}::{}", quoted, operator, placeholder, cast));
7791    }
7792
7793    // The path language is a query over the document rather than a comparison
7794    // against one, so it is a function of the column and the path: `_exists`
7795    // asks whether the path selects anything, `_match` whether the predicate
7796    // it ends in holds.
7797    if let Some(function) = match op {
7798        "_jsonb_path_exists" => Some("jsonb_path_exists"),
7799        "_jsonb_path_match" => Some("jsonb_path_match"),
7800        _ => None,
7801    } {
7802        let placeholder = format!("${}", param_idx);
7803        *param_idx += 1;
7804        values.push(operand.clone());
7805        return Ok(format!(
7806            "{}({}, {}::jsonpath)",
7807            function, quoted, placeholder
7808        ));
7809    }
7810
7811    // Comparisons binding exactly one parameter, by the SQL they become.
7812    let binary = match op {
7813        "_eq" => Some("="),
7814        "_neq" => Some("<>"),
7815        "_gt" => Some(">"),
7816        "_gte" => Some(">="),
7817        "_lt" => Some("<"),
7818        "_lte" => Some("<="),
7819        "_like" => Some("LIKE"),
7820        "_nlike" => Some("NOT LIKE"),
7821        "_ilike" => Some("ILIKE"),
7822        "_nilike" => Some("NOT ILIKE"),
7823        "_similar" => Some("SIMILAR TO"),
7824        "_nsimilar" => Some("NOT SIMILAR TO"),
7825        "_regex" => Some("~"),
7826        "_nregex" => Some("!~"),
7827        "_iregex" => Some("~*"),
7828        "_niregex" => Some("!~*"),
7829        "_contains" => Some("@>"),
7830        "_contained_in" => Some("<@"),
7831        "_has_key" => Some("?"),
7832        _ => None,
7833    };
7834
7835    if let Some(sql_op) = binary {
7836        let placeholder = format!("${}", param_idx);
7837        *param_idx += 1;
7838        // A containment operand is a whole document, and `"latest"` is one --
7839        // a JSON string, which is not the same text as `latest`. Binding the
7840        // bare string answered `invalid input syntax for type json`, so what
7841        // goes over the wire is the value as it is written in JSON.
7842        values.push(match op {
7843            "_contains" | "_contained_in" => match operand {
7844                serde_json::Value::String(_)
7845                | serde_json::Value::Number(_)
7846                | serde_json::Value::Bool(_) => serde_json::Value::String(operand.to_string()),
7847                other => other.clone(),
7848            },
7849            _ => operand.clone(),
7850        });
7851        // A bound parameter arrives as text and PostgreSQL infers a type for
7852        // it from the operator, which works only while the inference is
7853        // unambiguous. `jsonb @> text` is not an operator at all; and `id =
7854        // $1` against an integer column answers `operator does not exist:
7855        // integer = text` the moment the value is one PostgreSQL cannot read
7856        // as a number -- a null, most obviously, which is how a client asks a
7857        // question it expects an empty answer to.
7858        //
7859        // So the operand says what it is. The containment and key operators
7860        // name the type the operator needs; everything else names the
7861        // column's, which is the type it is being compared against.
7862        let cast = match op {
7863            "_contains" | "_contained_in" => "::jsonb".to_string(),
7864            "_has_key" => "::text".to_string(),
7865            // Only where inference would otherwise pick text and be wrong. A
7866            // cast tells PostgreSQL what the *result* is, and it infers the
7867            // parameter feeding it as text -- so `$1::int4` bound with a
7868            // number sends binary int8 bytes into a text parameter and
7869            // answers `invalid byte sequence for encoding "UTF8"`. A value
7870            // PostgreSQL can read as a number needs no help; a null is the one
7871            // that does, because `id = $1` with nothing to infer from is
7872            // `integer = text`.
7873            _ if operand.is_null() => match column_type {
7874                Some(pg_type) => format!("::{}", pg_type),
7875                None => String::new(),
7876            },
7877            // A parameter arrives as `text`, and PostgreSQL then resolves the
7878            // operator by taking the *column* down to text where it can --
7879            // which is the wrong operator, and sometimes no operator at all.
7880            // `citext = text` compares case-sensitively against a
7881            // case-insensitive column; `uuid = text` does not exist. So a
7882            // written value names the type it is being compared against.
7883            //
7884            // Only a string: a number bound with a cast is sent as binary into
7885            // a parameter PostgreSQL has inferred as text, which is the
7886            // `invalid byte sequence for encoding "UTF8"` this used to answer.
7887            _ if operand.is_string() => match column_type {
7888                Some(pg_type) => format!("::{}", pg_type),
7889                None => String::new(),
7890            },
7891            _ => String::new(),
7892        };
7893        return Ok(format!("{} {} {}{}", quoted, sql_op, placeholder, cast));
7894    }
7895
7896    match op {
7897        "_is_null" => Ok(if operand.as_bool().unwrap_or(false) {
7898            format!("{} IS NULL", quoted)
7899        } else {
7900            format!("{} IS NOT NULL", quoted)
7901        }),
7902        "_in" | "_nin" => {
7903            let items = operand.as_array().ok_or_else(|| {
7904                async_graphql::Error::new(format!(
7905                    "the \"{}\" comparison on \"{}\" requires a list of values",
7906                    op, column
7907                ))
7908            })?;
7909            if items.is_empty() {
7910                // `IN ()` is not valid SQL. An empty `_in` matches nothing and
7911                // an empty `_nin` matches everything.
7912                return Ok(if op == "_in" { "false" } else { "true" }.to_string());
7913            }
7914            let mut placeholders = Vec::with_capacity(items.len());
7915            for item in items {
7916                // Cast only a null, for the reason the binary comparisons
7917                // give: a cast makes PostgreSQL infer the parameter as text.
7918                let cast = match (item.is_null() || item.is_string(), column_type) {
7919                    (true, Some(pg_type)) => format!("::{}", pg_type),
7920                    _ => String::new(),
7921                };
7922                placeholders.push(format!("${}{}", param_idx, cast));
7923                values.push(item.clone());
7924                *param_idx += 1;
7925            }
7926            Ok(format!(
7927                "{} {} ({})",
7928                quoted,
7929                if op == "_in" { "IN" } else { "NOT IN" },
7930                placeholders.join(", ")
7931            ))
7932        }
7933        "_has_keys_any" | "_has_keys_all" => {
7934            let items = operand.as_array().ok_or_else(|| {
7935                async_graphql::Error::new(format!(
7936                    "the \"{}\" comparison on \"{}\" requires a list of keys",
7937                    op, column
7938                ))
7939            })?;
7940            Ok(format!(
7941                "{} {} {}",
7942                quoted,
7943                if op == "_has_keys_any" { "?|" } else { "?&" },
7944                sql_array(items, "text[]", param_idx, values)
7945            ))
7946        }
7947        other => Err(async_graphql::Error::new(format!(
7948            "unsupported comparison \"{}\" on \"{}\"",
7949            other, column
7950        ))),
7951    }
7952}
7953
7954/// Build an `ORDER BY` clause from the `order_by` argument.
7955///
7956/// The argument is a list of single-column objects -- `[{name: asc}, {id:
7957/// desc}]` -- and the list is ordered, which is why it is a list and not one
7958/// object with several keys. Column names are checked against the table in the
7959/// schema cache before being quoted, so a name that is unknown, or crafted to
7960/// inject SQL, is rejected rather than interpolated. Returns an empty string
7961/// when no ordering was requested.
7962#[allow(clippy::too_many_arguments)] // one parameter per SQL clause
7963async fn build_order_by_clause(
7964    ctx: &ResolverContext<'_>,
7965    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
7966    schema_name: &str,
7967    table_name: &str,
7968    type_name: &str,
7969    relationships: &HashMap<String, Vec<RelationshipField>>,
7970    reference: &str,
7971    names: &crate::names::NameOverrides,
7972) -> Result<String, async_graphql::Error> {
7973    let Ok(order_arg) = ctx.args.try_get("order_by") else {
7974        return Ok(String::new());
7975    };
7976
7977    let value = accessor_to_json(&order_arg);
7978    // A single object is accepted as a one-entry list, which is what a client
7979    // writing `order_by: {name: asc}` means and what Hasura reads it as.
7980    let entries: Vec<&serde_json::Value> = match &value {
7981        serde_json::Value::Array(items) => items.iter().collect(),
7982        serde_json::Value::Object(_) => vec![&value],
7983        serde_json::Value::Null => return Ok(String::new()),
7984        _ => {
7985            return Err(async_graphql::Error::new(
7986                "order_by takes an object or a list of them",
7987            ))
7988        }
7989    };
7990    if entries.is_empty() {
7991        return Ok(String::new());
7992    }
7993
7994    let guard = schema_cache
7995        .get()
7996        .await
7997        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7998    let cache = guard
7999        .as_ref()
8000        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8001    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8002    let table = cache
8003        .get_table(&qi)
8004        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8005
8006    let mut terms = Vec::new();
8007    let mut alias_counter = 0usize;
8008    for entry in entries {
8009        order_terms_into(
8010            entry,
8011            cache,
8012            relationships,
8013            type_name,
8014            table,
8015            reference,
8016            names,
8017            &mut alias_counter,
8018            &mut terms,
8019        )?;
8020    }
8021
8022    if terms.is_empty() {
8023        return Ok(String::new());
8024    }
8025    Ok(format!(" ORDER BY {}", terms.join(", ")))
8026}
8027
8028/// Collect the `ORDER BY` terms one entry contributes.
8029///
8030/// A key whose value is a direction is a column of this table. A key whose
8031/// value is an object is something the row points at, and ordering by it is a
8032/// correlated subselect: one related row contributes its column, and many
8033/// contribute an aggregate. That is the whole difference between the two
8034/// sides, and PostgreSQL will take a scalar subquery anywhere a column goes.
8035#[allow(clippy::too_many_arguments)]
8036fn order_terms_into(
8037    entry: &serde_json::Value,
8038    cache: &SchemaCache,
8039    relationships: &HashMap<String, Vec<RelationshipField>>,
8040    type_name: &str,
8041    table: &postrust_core::schema_cache::Table,
8042    reference: &str,
8043    names: &crate::names::NameOverrides,
8044    alias_counter: &mut usize,
8045    terms: &mut Vec<String>,
8046) -> Result<(), async_graphql::Error> {
8047    let serde_json::Value::Object(map) = entry else {
8048        return Err(async_graphql::Error::new(
8049            "each order_by entry is an object mapping a column to a direction",
8050        ));
8051    };
8052    let available: &[RelationshipField] = relationships
8053        .get(type_name)
8054        .map(|r| r.as_slice())
8055        .unwrap_or(&[]);
8056
8057    for (key, value) in map {
8058        // A direction given as null is no direction. `order_by: {id:
8059        // $direction}` with nothing bound for `$direction` is how a client
8060        // makes an ordering optional without writing the query twice, and the
8061        // same goes for `{author: $author_order_by}`, which is a whole
8062        // ordering left out.
8063        if value.is_null() {
8064            continue;
8065        }
8066
8067        // A direction: this table's own column, or a function of its row.
8068        if let Some(name) = value.as_str() {
8069            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8070                async_graphql::Error::new(format!(
8071                    "\"{}\" is not a sort direction; expected one of asc, desc, \
8072                     asc_nulls_first, asc_nulls_last, desc_nulls_first, desc_nulls_last",
8073                    name
8074                ))
8075            })?;
8076            let column = table_column_for(names, table, key);
8077            if table.get_column(column).is_some() {
8078                terms.push(format!(
8079                    "{}.{} {}",
8080                    reference,
8081                    postrust_sql::escape_ident(column),
8082                    sql
8083                ));
8084                continue;
8085            }
8086            // A computed field is a column as far as ordering is concerned:
8087            // the row is the argument, and the call is what is sorted on.
8088            let function = names
8089                .computed_source(&table.schema, &table.name, key)
8090                .unwrap_or(key);
8091            if let Some(definition) = table.get_computed_column(function) {
8092                terms.push(format!(
8093                    "{} {}",
8094                    computed_call(definition, &format!("{}.*", reference), reference),
8095                    sql
8096                ));
8097                continue;
8098            }
8099            return Err(async_graphql::Error::new(format!(
8100                "cannot order by unknown column \"{}\" on \"{}\"",
8101                key, table.name
8102            )));
8103        }
8104
8105        // An aggregate of the rows that point here.
8106        if let Some(rel) = key
8107            .strip_suffix("_aggregate")
8108            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list))
8109        {
8110            aggregate_order_terms(value, rel, cache, reference, alias_counter, terms)?;
8111            continue;
8112        }
8113
8114        // A column of the row this one points at.
8115        if let Some(rel) = available.iter().find(|r| r.name == *key && !r.is_list) {
8116            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8117                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8118            if plan.columns.is_empty() {
8119                return Err(async_graphql::Error::new(format!(
8120                    "cannot order by \"{}\": it is not reached by a key",
8121                    key
8122                )));
8123            }
8124            *alias_counter += 1;
8125            let alias = format!("pgrst_ord_{}", alias_counter);
8126            let quoted_alias = postrust_sql::escape_ident(&alias);
8127            let correlation = plan
8128                .columns
8129                .iter()
8130                .map(|(local, foreign)| {
8131                    format!(
8132                        "{} = {}.{}",
8133                        format_args!("{}.{}", reference, postrust_sql::escape_ident(local)),
8134                        quoted_alias,
8135                        postrust_sql::escape_ident(foreign)
8136                    )
8137                })
8138                .collect::<Vec<_>>()
8139                .join(" AND ");
8140
8141            let target_qi = postrust_core::api_request::QualifiedIdentifier::new(
8142                &plan.foreign_schema,
8143                &plan.foreign_table,
8144            );
8145            let Some(target) = cache.get_table(&target_qi) else {
8146                return Err(async_graphql::Error::new(format!(
8147                    "unknown table \"{}\"",
8148                    plan.foreign_table
8149                )));
8150            };
8151
8152            // The related row's own terms, then wrapped one at a time: a
8153            // subquery yields one value, so each term is its own subselect.
8154            let mut nested = Vec::new();
8155            order_terms_into(
8156                value,
8157                cache,
8158                relationships,
8159                &rel.target_type,
8160                target,
8161                &quoted_alias,
8162                names,
8163                alias_counter,
8164                &mut nested,
8165            )?;
8166            for term in nested {
8167                let (expression, direction) = term
8168                    .rsplit_once(' ')
8169                    .map(|(e, d)| (e.to_string(), d.to_string()))
8170                    .unwrap_or((term.clone(), String::new()));
8171                terms.push(format!(
8172                    "(SELECT {} FROM {}.{} AS {} WHERE {}) {}",
8173                    expression,
8174                    postrust_sql::escape_ident(&plan.foreign_schema),
8175                    postrust_sql::escape_ident(&plan.foreign_table),
8176                    quoted_alias,
8177                    correlation,
8178                    direction
8179                ));
8180            }
8181            continue;
8182        }
8183
8184        return Err(async_graphql::Error::new(format!(
8185            "cannot order by \"{}\" on \"{}\"",
8186            key, table.name
8187        )));
8188    }
8189    Ok(())
8190}
8191
8192/// The `ORDER BY` terms for an aggregate of a row's children.
8193fn aggregate_order_terms(
8194    value: &serde_json::Value,
8195    rel: &RelationshipField,
8196    cache: &SchemaCache,
8197    reference: &str,
8198    alias_counter: &mut usize,
8199    terms: &mut Vec<String>,
8200) -> Result<(), async_graphql::Error> {
8201    let serde_json::Value::Object(spec) = value else {
8202        return Err(async_graphql::Error::new(
8203            "ordering by an aggregate takes an object, such as {count: desc}",
8204        ));
8205    };
8206    let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8207        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8208    if plan.columns.is_empty() && plan.function.is_none() {
8209        return Err(async_graphql::Error::new(format!(
8210            "cannot order by an aggregate of \"{}\": it is not reached by a key",
8211            rel.name
8212        )));
8213    }
8214    // A computed relationship whose function takes more than the row cannot be
8215    // ordered by here: there is nowhere in `order_by` to write the arguments.
8216    if plan.function.is_some() && !rel.arguments.is_empty() {
8217        return Err(async_graphql::Error::new(format!(
8218            "cannot order by an aggregate of \"{}\": it takes arguments, and an \
8219             ordering has nowhere to write them",
8220            rel.name
8221        )));
8222    }
8223
8224    *alias_counter += 1;
8225    let alias = postrust_sql::escape_ident(&format!("pgrst_ord_{}", alias_counter));
8226    // A computed relationship is correlated by argument -- the function takes
8227    // the parent row -- where a key relationship is correlated by columns.
8228    let (source, correlation) = match &plan.function {
8229        Some(function) => {
8230            let session = plan.row_argument.as_ref().and_then(|row_argument| {
8231                computed_session_argument(cache, function, row_argument)
8232                    .map(|session| (row_argument.clone(), session))
8233            });
8234            let call = match session {
8235                Some((row_argument, session)) => format!(
8236                    "{} => {}, {} => {}",
8237                    postrust_sql::escape_ident(&row_argument),
8238                    reference,
8239                    postrust_sql::escape_ident(&session),
8240                    SESSION_ARGUMENT
8241                ),
8242                None => reference.to_string(),
8243            };
8244            (
8245                format!(
8246                    "{}.{}({})",
8247                    postrust_sql::escape_ident(&function.schema),
8248                    postrust_sql::escape_ident(&function.name),
8249                    call
8250                ),
8251                "true".to_string(),
8252            )
8253        }
8254        None => (
8255            format!(
8256                "{}.{}",
8257                postrust_sql::escape_ident(&plan.foreign_schema),
8258                postrust_sql::escape_ident(&plan.foreign_table)
8259            ),
8260            plan.columns
8261                .iter()
8262                .map(|(local, foreign)| {
8263                    format!(
8264                        "{}.{} = {}.{}",
8265                        reference,
8266                        postrust_sql::escape_ident(local),
8267                        alias,
8268                        postrust_sql::escape_ident(foreign)
8269                    )
8270                })
8271                .collect::<Vec<_>>()
8272                .join(" AND "),
8273        ),
8274    };
8275
8276    // `{count: desc}` is one term; `{max: {id: desc}}` is one per column.
8277    let mut wanted: Vec<(String, String)> = Vec::new();
8278    for (function, argument) in spec {
8279        // A direction given as null is no direction, as everywhere else an
8280        // ordering is read.
8281        if argument.is_null() {
8282            continue;
8283        }
8284        if function == "count" {
8285            let name = argument.as_str().unwrap_or_default();
8286            let Some(direction) = crate::input::order_by::direction_sql(name) else {
8287                return Err(async_graphql::Error::new(format!(
8288                    "\"{}\" is not a sort direction",
8289                    name
8290                )));
8291            };
8292            wanted.push(("count(*)".to_string(), direction.to_string()));
8293            continue;
8294        }
8295        let serde_json::Value::Object(columns) = argument else {
8296            continue;
8297        };
8298        for (column, direction) in columns {
8299            if direction.is_null() {
8300                continue;
8301            }
8302            let name = direction.as_str().unwrap_or_default();
8303            let Some(direction) = crate::input::order_by::direction_sql(name) else {
8304                return Err(async_graphql::Error::new(format!(
8305                    "\"{}\" is not a sort direction",
8306                    name
8307                )));
8308            };
8309            // The function comes from the generated input, not from the
8310            // request: a client can only name one that was offered.
8311            wanted.push((
8312                format!(
8313                    "{}({}.{})",
8314                    function,
8315                    alias,
8316                    postrust_sql::escape_ident(column)
8317                ),
8318                direction.to_string(),
8319            ));
8320        }
8321    }
8322
8323    for (expression, direction) in wanted {
8324        terms.push(format!(
8325            "(SELECT {} FROM {} AS {} WHERE {}) {}",
8326            expression, source, alias, correlation, direction
8327        ));
8328    }
8329    Ok(())
8330}
8331
8332/// The `DISTINCT ON (...)` prefix, and the ordering that has to go with it.
8333///
8334/// PostgreSQL keeps the first row of each DISTINCT ON group in the query's own
8335/// order, and picks arbitrarily where the ordering does not begin with the
8336/// distinct columns -- so which row survives would depend on the plan. Hasura
8337/// refuses that query rather than answering it, and so does this: prepending
8338/// the distinct columns instead produced an answer, and a wrong one, since
8339/// `ORDER BY "department", "department" DESC` is decided by its first term and
8340/// sorts ascending. Where nothing was ordered at all there is nothing to
8341/// disagree with, so the distinct columns become the ordering.
8342///
8343/// `written` is the ordering as its comma-separated terms, without the
8344/// keyword; the answer is in the same spelling.
8345fn distinct_on_clause(
8346    distinct_on: &[String],
8347    written: Option<&str>,
8348) -> Result<(String, Option<String>), async_graphql::Error> {
8349    if distinct_on.is_empty() {
8350        return Ok((String::new(), written.map(str::to_string)));
8351    }
8352    let terms: Vec<&str> = written
8353        .map(|written| written.split(", ").collect())
8354        .unwrap_or_default();
8355    // Terms are qualified and the distinct columns may not be, so they are
8356    // compared by the name itself.
8357    let column_of = |term: &str| {
8358        let expression = term.split_whitespace().next().unwrap_or(term);
8359        expression
8360            .rsplit_once('.')
8361            .map(|(_, name)| name)
8362            .unwrap_or(expression)
8363            .to_string()
8364    };
8365    let leading: Vec<String> = terms
8366        .iter()
8367        .take(distinct_on.len())
8368        .map(|term| column_of(term))
8369        .collect();
8370    if !terms.is_empty()
8371        && (leading.len() != distinct_on.len()
8372            || distinct_on
8373                .iter()
8374                .any(|column| !leading.contains(&column_of(column))))
8375    {
8376        return Err(validation_error(
8377            "\"distinct_on\" columns must match initial \"order_by\" columns",
8378        ));
8379    }
8380    let order = match terms.is_empty() {
8381        true => Some(distinct_on.join(", ")),
8382        false => written.map(str::to_string),
8383    };
8384    Ok((format!("DISTINCT ON ({}) ", distinct_on.join(", ")), order))
8385}
8386
8387/// Build the `DISTINCT ON (...)` prefix from the `distinct_on` argument.
8388///
8389/// PostgreSQL requires the leftmost `ORDER BY` terms to match the distinct
8390/// columns, and picks an arbitrary surviving row otherwise. The caller
8391/// prepends these terms to whatever ordering was asked for rather than
8392/// replacing it, so `distinct_on: [name]` with `order_by: [{id: desc}]` keeps
8393/// the highest id per name instead of an unpredictable one.
8394async fn build_distinct_on(
8395    ctx: &ResolverContext<'_>,
8396    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8397    schema_name: &str,
8398    table_name: &str,
8399    names: &crate::names::NameOverrides,
8400) -> Result<Vec<String>, async_graphql::Error> {
8401    let Ok(arg) = ctx.args.try_get("distinct_on") else {
8402        return Ok(Vec::new());
8403    };
8404    let value = accessor_to_json(&arg);
8405    let requested: Vec<String> = match &value {
8406        serde_json::Value::Array(items) => items
8407            .iter()
8408            .filter_map(|i| i.as_str().map(|s| s.to_string()))
8409            .collect(),
8410        serde_json::Value::String(one) => vec![one.clone()],
8411        _ => Vec::new(),
8412    };
8413    if requested.is_empty() {
8414        return Ok(Vec::new());
8415    }
8416
8417    let guard = schema_cache
8418        .get()
8419        .await
8420        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8421    let cache = guard
8422        .as_ref()
8423        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8424    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8425    let table = cache
8426        .get_table(&qi)
8427        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8428
8429    let mut quoted = Vec::with_capacity(requested.len());
8430    for field in requested {
8431        let name = table_column_for(names, table, &field).to_string();
8432        if table.get_column(&name).is_none() {
8433            return Err(async_graphql::Error::new(format!(
8434                "cannot take distinct on unknown column \"{}\" of \"{}\"",
8435                field, table_name
8436            )));
8437        }
8438        quoted.push(postrust_sql::escape_ident(&name));
8439    }
8440    Ok(quoted)
8441}
8442
8443/// Build the SELECT-list expressions that embed relationships in one query.
8444///
8445/// The GraphQL mirror of the REST builder: each requested relationship becomes a
8446/// correlated subselect yielding JSON, so the whole selection comes back from
8447/// the parent query instead of one query per relationship per level.
8448/// The SELECT-list entries for any computed columns the selection asks for.
8449///
8450/// A computed column is a function of one argument of the table's own row
8451/// type, so it is not in `*` and has to be named. PostgreSQL's functional
8452/// notation reads `upper_name(author.*)` and `author.upper_name` as the same
8453/// call; the explicit form is written here because it says which function is
8454/// being called.
8455#[allow(clippy::too_many_arguments)] // the fields, their source, and the binding state
8456fn computed_projections<'a>(
8457    table: &postrust_core::schema_cache::Table,
8458    fields: impl IntoIterator<Item = SelectionField<'a>>,
8459    row_reference: &str,
8460    names: &crate::names::NameOverrides,
8461    schema_cache: &SchemaCache,
8462    param_idx: &mut usize,
8463    values: &mut Vec<serde_json::Value>,
8464) -> Result<Vec<String>, async_graphql::Error> {
8465    let mut projections = Vec::new();
8466    for field in fields {
8467        let name = field.name();
8468        // A real column wins, and the projection already carries it -- under
8469        // this name, whether or not that is the column's own.
8470        if table
8471            .get_column(table_column_for(names, table, name))
8472            .is_some()
8473        {
8474            continue;
8475        }
8476        // The field may be exposed under a name that was given rather than
8477        // the function's own, so the call is looked up from either side.
8478        let function = names
8479            .computed_source(&table.schema, &table.name, name)
8480            .unwrap_or(name);
8481        let Some(computed) = table.get_computed_column(function) else {
8482            continue;
8483        };
8484        let arguments = crate::schema::computed_caller_arguments(computed, schema_cache);
8485        let given = match arguments.is_empty() {
8486            true => None,
8487            false => embed_arguments(field).remove("args"),
8488        };
8489        projections.push(format!(
8490            "{} AS {}",
8491            computed_column_call(
8492                computed,
8493                row_reference,
8494                name,
8495                &arguments,
8496                given.as_ref(),
8497                param_idx,
8498                values,
8499            )?,
8500            postrust_sql::escape_ident(name)
8501        ));
8502    }
8503    Ok(projections)
8504}
8505
8506/// The call behind a computed field, including whatever the caller passed.
8507///
8508/// The ordinary case is a function of the row alone, which is written
8509/// positionally: that is the call REST writes, and the one a client that never
8510/// heard of `args` is asking for. Anything else has to be written by name --
8511/// the row is then not the only parameter and may not be the first -- so the
8512/// row, the session where the function asks for one, and each argument the
8513/// caller gave are all named.
8514fn computed_column_call(
8515    computed: &postrust_core::schema_cache::ComputedColumn,
8516    row: &str,
8517    field_name: &str,
8518    arguments: &[(String, String, bool)],
8519    given: Option<&serde_json::Value>,
8520    param_idx: &mut usize,
8521    values: &mut Vec<serde_json::Value>,
8522) -> Result<String, async_graphql::Error> {
8523    if arguments.is_empty() {
8524        return Ok(computed_call(computed, row, row));
8525    }
8526    let Some(row_argument) = &computed.row_argument else {
8527        return Ok(computed_call(computed, row, row));
8528    };
8529    let function = format!(
8530        "{}.{}",
8531        postrust_sql::escape_ident(&computed.function.schema),
8532        postrust_sql::escape_ident(&computed.function.name)
8533    );
8534    let mut passed = vec![format!(
8535        "{} => {}",
8536        postrust_sql::escape_ident(row_argument),
8537        row
8538    )];
8539    if let Some(session) = &computed.session_argument {
8540        passed.push(format!(
8541            "{} => {}",
8542            postrust_sql::escape_ident(session),
8543            SESSION_ARGUMENT
8544        ));
8545    }
8546    for (name, pg_type, required) in arguments {
8547        match given.and_then(|given| given.get(name)) {
8548            None if *required => {
8549                return Err(async_graphql::Error::new(format!(
8550                    "{} needs the argument \"{}\"",
8551                    field_name, name
8552                )))
8553            }
8554            // Left out is left out, so the function's own default applies.
8555            None => continue,
8556            Some(value) => {
8557                passed.push(format!(
8558                    "{} => ${}::{}",
8559                    postrust_sql::escape_ident(name),
8560                    param_idx,
8561                    pg_type
8562                ));
8563                *param_idx += 1;
8564                values.push(value.clone());
8565            }
8566        }
8567    }
8568    Ok(format!("{}({})", function, passed.join(", ")))
8569}
8570
8571/// The `count(...)` a selection asked for.
8572///
8573/// `count` alone is `count(*)`, which counts rows. `count(columns: [a])` is
8574/// `count("a")`, which counts the rows where `a` is not null -- a different
8575/// number, and the one the client asked for. `distinct: true` counts the
8576/// distinct values among them, and several columns are counted as the row
8577/// they make, which is how PostgreSQL counts more than one thing at once.
8578///
8579/// The column names come from the generated enum, so a selection can only
8580/// name a column the table has; what it may not know is the column's own name
8581/// where the schema exposes it under another.
8582fn count_expression(
8583    field: async_graphql::SelectionField<'_>,
8584    table: (&str, &str),
8585    alias: Option<&str>,
8586    names: &crate::names::NameOverrides,
8587) -> String {
8588    let arguments = embed_arguments(field);
8589    let requested = match arguments.get("columns") {
8590        Some(serde_json::Value::Array(items)) => items
8591            .iter()
8592            .filter_map(|item| item.as_str())
8593            .collect::<Vec<_>>(),
8594        Some(serde_json::Value::String(one)) => vec![one.as_str()],
8595        _ => Vec::new(),
8596    };
8597    if requested.is_empty() {
8598        return "count(*)".to_string();
8599    }
8600    let columns: Vec<String> = requested
8601        .into_iter()
8602        .map(|column| {
8603            let (schema, name) = table;
8604            let source = column_for(names, schema, name, column);
8605            match alias {
8606                Some(alias) => format!(
8607                    "{}.{}",
8608                    postrust_sql::escape_ident(alias),
8609                    postrust_sql::escape_ident(source)
8610                ),
8611                None => postrust_sql::escape_ident(source),
8612            }
8613        })
8614        .collect();
8615    // One column counts itself; several count the row they make, since
8616    // `count(a, b)` is not a call PostgreSQL has.
8617    let counted = match columns.len() {
8618        1 => columns.into_iter().next().unwrap_or_default(),
8619        _ => format!("({})", columns.join(", ")),
8620    };
8621    let distinct = matches!(
8622        arguments.get("distinct"),
8623        Some(serde_json::Value::Bool(true))
8624    );
8625    match distinct {
8626        true => format!("count(DISTINCT {})", counted),
8627        false => format!("count({})", counted),
8628    }
8629}
8630
8631/// The SELECT list for a nested aggregate.
8632///
8633/// `articles_aggregate { aggregate { count } nodes { title } }` becomes one
8634/// row per parent, correlated the way any embed is. Both halves are aggregates
8635/// over the same correlated set, so they go in one select list rather than two
8636/// queries -- `count(*)` and `json_agg(...)` read the same rows.
8637#[allow(clippy::too_many_arguments)] // the recursion carries its whole context
8638fn nested_aggregate_select(
8639    caller: &crate::role::Caller<'_>,
8640    selection: async_graphql::SelectionField<'_>,
8641    child_alias: &str,
8642    schema_cache: &SchemaCache,
8643    relationships: &HashMap<String, Vec<RelationshipField>>,
8644    target_type: &str,
8645    child_table: Option<&postrust_core::schema_cache::Table>,
8646    max_rows: Option<i64>,
8647    alias_counter: &mut usize,
8648    param_idx: &mut usize,
8649    values: &mut Vec<serde_json::Value>,
8650    names: &crate::names::NameOverrides,
8651) -> Result<String, async_graphql::Error> {
8652    use crate::schema::aggregate as agg;
8653
8654    let mut parts: Vec<String> = Vec::new();
8655
8656    for child in selection.selection_set() {
8657        match child.name() {
8658            "aggregate" => {
8659                let of_table = child_table
8660                    .map(|table| (table.schema.as_str(), table.name.as_str()))
8661                    .unwrap_or(("", ""));
8662                // Each `count` is answered under the name it was asked for,
8663                // since two of them may count different things. Every other
8664                // aggregate is one object read under however many names, so
8665                // its columns are the union of what all of them named.
8666                let mut counts: Vec<(String, String)> = Vec::new();
8667                let mut wanted: BTreeMap<&str, Vec<String>> = BTreeMap::new();
8668                for function in child.selection_set() {
8669                    if function.name() == "count" {
8670                        counts.push((
8671                            function.alias().unwrap_or("count").to_string(),
8672                            count_expression(function, of_table, Some(child_alias), names),
8673                        ));
8674                        continue;
8675                    }
8676                    let sql_function = agg::NUMERIC_AGGREGATES
8677                        .iter()
8678                        .chain(agg::ORDERED_AGGREGATES.iter())
8679                        .find(|(name, _)| *name == function.name())
8680                        .map(|(name, _)| *name);
8681                    let Some(sql_function) = sql_function else {
8682                        continue;
8683                    };
8684                    let columns = wanted.entry(sql_function).or_default();
8685                    for column in function.selection_set() {
8686                        let name = column.name().to_string();
8687                        if !columns.contains(&name) {
8688                            columns.push(name);
8689                        }
8690                    }
8691                }
8692                // Answered whether or not it was asked for, for the reason
8693                // the root aggregate answers it.
8694                let mut fields = match counts.iter().any(|(key, _)| key == "count") {
8695                    true => Vec::new(),
8696                    false => vec!["'count', count(*)".to_string()],
8697                };
8698                for (key, expression) in &counts {
8699                    fields.push(format!("'{}', {}", key.replace('\'', "''"), expression));
8700                }
8701                for (sql_function, named) in &wanted {
8702                    let columns: Vec<String> = named
8703                        .iter()
8704                        .map(|column| {
8705                            let source = child_table
8706                                .map(|table| table_column_for(names, table, column))
8707                                .unwrap_or(column.as_str());
8708                            format!(
8709                                "'{}', {}({}.{})",
8710                                column.replace('\'', "''"),
8711                                sql_function,
8712                                postrust_sql::escape_ident(child_alias),
8713                                postrust_sql::escape_ident(source)
8714                            )
8715                        })
8716                        .collect();
8717                    if !columns.is_empty() {
8718                        fields.push(format!(
8719                            "'{}', json_build_object({})",
8720                            sql_function,
8721                            columns.join(", ")
8722                        ));
8723                    }
8724                }
8725                parts.push(format!(
8726                    "json_build_object({}) AS {}",
8727                    fields.join(", "),
8728                    postrust_sql::escape_ident("aggregate")
8729                ));
8730            }
8731            // `nodes` under an aggregate is a rows selection like any other:
8732            // what it names may be a column, a computed field, or a
8733            // relationship of its own, and the last of those is a further
8734            // correlated subselect rather than a column of this one.
8735            "nodes" => {
8736                let embeds = build_embed_expressions(
8737                    caller,
8738                    schema_cache,
8739                    relationships,
8740                    target_type,
8741                    child_alias,
8742                    child.selection_set(),
8743                    max_rows,
8744                    alias_counter,
8745                    param_idx,
8746                    values,
8747                    names,
8748                )?;
8749                let mut columns: Vec<String> = Vec::new();
8750                for column in child.selection_set() {
8751                    let name = column.name();
8752                    if name == "__typename" || embeds.iter().any(|(field, _)| field == name) {
8753                        continue;
8754                    }
8755                    columns.push(format!(
8756                        "'{}', {}",
8757                        name.replace('\'', "''"),
8758                        column_expression(child_table, child_alias, name, names)
8759                    ));
8760                }
8761                for (field, expression) in &embeds {
8762                    columns.push(format!("'{}', {}", field.replace('\'', "''"), expression));
8763                }
8764                let row = if columns.is_empty() {
8765                    format!("row_to_json({})", postrust_sql::escape_ident(child_alias))
8766                } else {
8767                    format!("json_build_object({})", columns.join(", "))
8768                };
8769                parts.push(format!(
8770                    "COALESCE(json_agg({}), '[]'::json) AS {}",
8771                    row,
8772                    postrust_sql::escape_ident("nodes")
8773                ));
8774            }
8775            _ => {}
8776        }
8777    }
8778
8779    if parts.is_empty() {
8780        // A selection of nothing but `__typename`. One row, no columns, is not
8781        // valid SQL; a count nobody reads is.
8782        parts.push("count(*) AS pgrst_empty".to_string());
8783    }
8784    Ok(parts.join(", "))
8785}
8786
8787/// How one named field of a row is read, given the alias the row goes by.
8788///
8789/// A column is itself; a computed field is the call that produces it. Which of
8790/// the two a name is depends on the table, so this is the one place that asks.
8791fn column_expression(
8792    table: Option<&postrust_core::schema_cache::Table>,
8793    alias: &str,
8794    name: &str,
8795    names: &crate::names::NameOverrides,
8796) -> String {
8797    let column = table
8798        .map(|t| table_column_for(names, t, name))
8799        .unwrap_or(name);
8800    let plain = format!(
8801        "{}.{}",
8802        postrust_sql::escape_ident(alias),
8803        postrust_sql::escape_ident(column)
8804    );
8805    let computed = table
8806        .filter(|t| t.get_column(column).is_none())
8807        .and_then(|t| {
8808            let function = names
8809                .computed_source(&t.schema, &t.name, name)
8810                .unwrap_or(name);
8811            t.get_computed_column(function)
8812        });
8813    match computed {
8814        Some(definition) => computed_call(
8815            definition,
8816            &format!("{}.*", postrust_sql::escape_ident(alias)),
8817            &postrust_sql::escape_ident(alias),
8818        ),
8819        None => plain,
8820    }
8821}
8822
8823/// Render `order_by` terms against a table, qualified with an alias.
8824///
8825/// The root field's ordering reads its argument from the resolver context and
8826/// needs the schema cache asynchronously; an embed already holds both, so this
8827/// takes the value directly. Columns are checked against the table before they
8828/// are quoted, so an unknown or crafted name is refused rather than
8829/// interpolated.
8830fn order_terms(
8831    order: &serde_json::Value,
8832    schema_cache: &SchemaCache,
8833    schema_name: &str,
8834    table_name: &str,
8835    alias: &str,
8836    names: &crate::names::NameOverrides,
8837) -> Result<Option<String>, async_graphql::Error> {
8838    let entries: Vec<&serde_json::Value> = match order {
8839        serde_json::Value::Array(items) => items.iter().collect(),
8840        serde_json::Value::Object(_) => vec![order],
8841        _ => return Ok(None),
8842    };
8843
8844    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8845    let table = schema_cache
8846        .get_table(&qi)
8847        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8848
8849    let mut terms = Vec::new();
8850    for entry in entries {
8851        let serde_json::Value::Object(map) = entry else {
8852            continue;
8853        };
8854        for (field, direction) in map {
8855            // A direction given as null is no direction, as at the root.
8856            if direction.is_null() {
8857                continue;
8858            }
8859            let column = table_column_for(names, table, field);
8860            if table.get_column(column).is_none() {
8861                return Err(async_graphql::Error::new(format!(
8862                    "cannot order by unknown column \"{}\" on \"{}\"",
8863                    field, table_name
8864                )));
8865            }
8866            let name = direction.as_str().unwrap_or_default();
8867            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8868                async_graphql::Error::new(format!("\"{}\" is not a sort direction", name))
8869            })?;
8870            terms.push(format!(
8871                "{}.{} {}",
8872                postrust_sql::escape_ident(alias),
8873                postrust_sql::escape_ident(column),
8874                sql
8875            ));
8876        }
8877    }
8878
8879    Ok(if terms.is_empty() {
8880        None
8881    } else {
8882        Some(terms.join(", "))
8883    })
8884}
8885
8886/// Whether a to-one relationship always has a row at the other end.
8887///
8888/// It does when this row's own key columns cannot be null: a foreign key
8889/// guarantees a matching row for every value, so a `NOT NULL` key is a
8890/// relationship that cannot be absent, and the field is non-null. A client
8891/// generating types then gets `author` rather than `author | null`, which is
8892/// what the schema actually promises.
8893///
8894/// Only where a foreign key says so. A computed relationship is a function
8895/// call, and a one-to-one reached through the *child's* key is a row that may
8896/// simply not exist.
8897fn always_present(rel: &RelationshipField, table: &postrust_core::schema_cache::Table) -> bool {
8898    use postrust_core::schema_cache::{Cardinality, Relationship};
8899    let Relationship::ForeignKey { cardinality, .. } = &rel.relationship else {
8900        return false;
8901    };
8902    match cardinality {
8903        Cardinality::M2O { .. } => {}
8904        Cardinality::O2O {
8905            is_parent: false, ..
8906        } => {}
8907        _ => return false,
8908    }
8909    let columns = cardinality.columns();
8910    !columns.is_empty()
8911        && columns.iter().all(|(local, _)| {
8912            table
8913                .get_column(local)
8914                .is_some_and(|column| !column.nullable)
8915        })
8916}
8917
8918/// Whether the row at the other end of a relationship carries the key.
8919///
8920/// If it does, this row is written first and its key is pushed down; if this
8921/// row's own column carries it, the other row is written first and its key is
8922/// read back. Cardinality alone does not answer it: a one-to-one is one row in
8923/// either direction, and which side holds the foreign key is the whole
8924/// difference between the two orders.
8925fn child_holds_the_key(relationship: &postrust_core::schema_cache::Relationship) -> bool {
8926    use postrust_core::schema_cache::{Cardinality, Relationship};
8927    match relationship {
8928        Relationship::ForeignKey { cardinality, .. } => match cardinality {
8929            Cardinality::M2O { .. } => false,
8930            Cardinality::O2O { is_parent, .. } => *is_parent,
8931            _ => true,
8932        },
8933        Relationship::Computed { .. } => true,
8934    }
8935}
8936
8937/// The name of a function's session parameter, if it has one.
8938fn computed_session_argument(
8939    cache: &SchemaCache,
8940    function: &postrust_core::api_request::QualifiedIdentifier,
8941    row_argument: &str,
8942) -> Option<String> {
8943    let _ = row_argument;
8944    cache
8945        .routines
8946        .get(function)
8947        .into_iter()
8948        .flatten()
8949        .find(|routine| routine.name == function.name)?
8950        .params
8951        .iter()
8952        .find(|param| {
8953            param.name == "hasura_session" && matches!(param.param_type.as_str(), "json" | "jsonb")
8954        })
8955        .map(|param| param.name.clone())
8956}
8957
8958/// The name of the input holding a computed relationship's own arguments.
8959fn computed_args_type_name(type_name: &str, field: &str) -> String {
8960    format!("{}_{}_args", type_name, field)
8961}
8962
8963/// The argument list a computed relationship's function is called with.
8964///
8965/// `None` where the parent row is the only thing it takes -- the ordinary case,
8966/// and the positional call the REST surface writes. Anything else is written by
8967/// name: the row, the session where the function asks for one, and whatever the
8968/// client put under `args`.
8969fn computed_arguments(
8970    relationship: &RelationshipField,
8971    plan: &postrust_core::embed::EmbedPlan,
8972    parent_row: &str,
8973    given: Option<&serde_json::Value>,
8974    param_idx: &mut usize,
8975    values: &mut Vec<serde_json::Value>,
8976    schema_cache: &SchemaCache,
8977) -> Result<Option<String>, async_graphql::Error> {
8978    let (Some(function), Some(row_argument)) = (&plan.function, &plan.row_argument) else {
8979        return Ok(None);
8980    };
8981    let session = schema_cache
8982        .routines
8983        .get(function)
8984        .into_iter()
8985        .flatten()
8986        .find(|routine| routine.name == function.name)
8987        .and_then(|routine| {
8988            routine
8989                .params
8990                .iter()
8991                .find(|param| {
8992                    param.name == "hasura_session"
8993                        && matches!(param.param_type.as_str(), "json" | "jsonb")
8994                })
8995                .map(|param| param.name.clone())
8996        });
8997    if relationship.arguments.is_empty() && session.is_none() {
8998        return Ok(None);
8999    }
9000
9001    let mut passed = vec![format!(
9002        "{} => {}",
9003        postrust_sql::escape_ident(row_argument),
9004        parent_row
9005    )];
9006    if let Some(session) = session {
9007        passed.push(format!(
9008            "{} => {}",
9009            postrust_sql::escape_ident(&session),
9010            SESSION_ARGUMENT
9011        ));
9012    }
9013    for (name, pg_type, required) in &relationship.arguments {
9014        match given.and_then(|given| given.get(name)) {
9015            None if *required => {
9016                return Err(async_graphql::Error::new(format!(
9017                    "{} needs the argument \"{}\"",
9018                    relationship.name, name
9019                )))
9020            }
9021            // Left out is left out, so the function's own default applies.
9022            None => continue,
9023            Some(value) => {
9024                passed.push(format!(
9025                    "{} => ${}::{}",
9026                    postrust_sql::escape_ident(name),
9027                    param_idx,
9028                    pg_type
9029                ));
9030                *param_idx += 1;
9031                values.push(value.clone());
9032            }
9033        }
9034    }
9035    Ok(Some(passed.join(", ")))
9036}
9037
9038/// The arguments written on an embedded field, as JSON.
9039fn embed_arguments(field: async_graphql::SelectionField<'_>) -> HashMap<String, serde_json::Value> {
9040    field
9041        .arguments()
9042        .map(|args| {
9043            args.into_iter()
9044                .map(|(name, value)| {
9045                    (
9046                        name.to_string(),
9047                        plain_numbers(value.into_json().unwrap_or(serde_json::Value::Null)),
9048                    )
9049                })
9050                .collect()
9051        })
9052        .unwrap_or_default()
9053}
9054
9055/// What an embed's arguments narrow its child rows to: a `DISTINCT ON`
9056/// prefix, a predicate, an ordering, a limit and an offset, in the order the
9057/// SQL takes them.
9058type Narrowing = (String, Option<String>, Option<String>, Option<i64>, i64);
9059
9060/// What an embed's arguments narrow its child rows to.
9061///
9062/// A list and an aggregate of that same list take the same four arguments and
9063/// mean the same thing by them; only what is done with the surviving rows
9064/// differs, so the reading of the arguments is shared.
9065#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
9066fn embed_narrowing(
9067    arguments: &HashMap<String, serde_json::Value>,
9068    plan: &postrust_core::embed::EmbedPlan,
9069    schema_cache: &SchemaCache,
9070    relationships: &HashMap<String, Vec<RelationshipField>>,
9071    target_type: &str,
9072    child_alias: &str,
9073    max_rows: Option<i64>,
9074    param_idx: &mut usize,
9075    values: &mut Vec<serde_json::Value>,
9076    names: &crate::names::NameOverrides,
9077    caller: &crate::role::Caller<'_>,
9078) -> Result<Narrowing, async_graphql::Error> {
9079    // The child's own permission, which is the whole reason an embed is a
9080    // place this has to be applied: `author { articles { ... } }` reads two
9081    // tables, and the rule on the second is not the rule on the first.
9082    let permission =
9083        permission_predicate(caller, names, &plan.foreign_schema, &plan.foreign_table)?;
9084    let requested = arguments.get("where").filter(|v| !v.is_null()).cloned();
9085    let child_where = match and_predicates(requested, permission) {
9086        Some(expression) => {
9087            let child_scope = WhereScope::for_alias(
9088                &plan.foreign_schema,
9089                &plan.foreign_table,
9090                child_alias,
9091                target_type,
9092                schema_cache,
9093                relationships,
9094                names,
9095            )
9096            .for_caller(*caller);
9097            let mut nested_alias = 0usize;
9098            build_condition(
9099                &expression,
9100                &child_scope,
9101                param_idx,
9102                values,
9103                &mut nested_alias,
9104            )?
9105        }
9106        None => None,
9107    };
9108
9109    let child_order = match arguments.get("order_by") {
9110        Some(order) if !order.is_null() => order_terms(
9111            order,
9112            schema_cache,
9113            &plan.foreign_schema,
9114            &plan.foreign_table,
9115            child_alias,
9116            names,
9117        )?,
9118        _ => None,
9119    };
9120
9121    // A limit written on the embed is what the client asked for; the
9122    // configured ceiling still applies as an upper bound, the same way it
9123    // does at the top level -- and so does the child's own permission, which
9124    // bounds rows per parent here rather than per query.
9125    let ceiling = match (
9126        max_rows,
9127        crate::role::read_limit(caller, names, &plan.foreign_schema, &plan.foreign_table),
9128    ) {
9129        (Some(configured), Some(granted)) => Some(configured.min(granted)),
9130        (only, None) | (None, only) => only,
9131    };
9132    let child_limit = match arguments.get("limit").and_then(|v| v.as_i64()) {
9133        Some(requested) => match ceiling {
9134            Some(ceiling) => Some(requested.min(ceiling)),
9135            None => Some(requested),
9136        },
9137        None => ceiling,
9138    };
9139    let child_offset = arguments
9140        .get("offset")
9141        .and_then(|v| v.as_i64())
9142        .unwrap_or(0);
9143
9144    // `author { articles(distinct_on: [title], order_by: {title: asc}) }` --
9145    // one article per title, the same question the root field answers and
9146    // under the same rule about what may be ordered by.
9147    let requested = match arguments.get("distinct_on") {
9148        Some(serde_json::Value::Array(items)) => items
9149            .iter()
9150            .filter_map(|item| item.as_str())
9151            .collect::<Vec<_>>(),
9152        Some(serde_json::Value::String(one)) => vec![one.as_str()],
9153        _ => Vec::new(),
9154    };
9155    let mut distinct_on = Vec::with_capacity(requested.len());
9156    if !requested.is_empty() {
9157        let qi = postrust_core::api_request::QualifiedIdentifier::new(
9158            &plan.foreign_schema,
9159            &plan.foreign_table,
9160        );
9161        let table = schema_cache.get_table(&qi).ok_or_else(|| {
9162            async_graphql::Error::new(format!("unknown table \"{}\"", plan.foreign_table))
9163        })?;
9164        for field in requested {
9165            let column = table_column_for(names, table, field).to_string();
9166            if table.get_column(&column).is_none() {
9167                return Err(async_graphql::Error::new(format!(
9168                    "cannot take distinct on unknown column \"{}\" of \"{}\"",
9169                    field, plan.foreign_table
9170                )));
9171            }
9172            distinct_on.push(format!(
9173                "{}.{}",
9174                postrust_sql::escape_ident(child_alias),
9175                postrust_sql::escape_ident(&column)
9176            ));
9177        }
9178    }
9179    let (distinct, child_order) = distinct_on_clause(&distinct_on, child_order.as_deref())?;
9180
9181    Ok((
9182        distinct,
9183        child_where,
9184        child_order,
9185        child_limit,
9186        child_offset,
9187    ))
9188}
9189
9190#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
9191fn build_embed_expressions<'a>(
9192    caller: &crate::role::Caller<'_>,
9193    schema_cache: &SchemaCache,
9194    relationships: &HashMap<String, Vec<RelationshipField>>,
9195    type_name: &str,
9196    parent_alias: &str,
9197    fields: impl IntoIterator<Item = SelectionField<'a>>,
9198    max_rows: Option<i64>,
9199    alias_counter: &mut usize,
9200    param_idx: &mut usize,
9201    values: &mut Vec<serde_json::Value>,
9202    names: &crate::names::NameOverrides,
9203) -> Result<Vec<(String, String)>, async_graphql::Error> {
9204    let Some(available) = relationships.get(type_name) else {
9205        return Ok(Vec::new());
9206    };
9207
9208    let mut expressions = Vec::new();
9209
9210    for field in fields {
9211        // `<relationship>_aggregate` is the same embed with an aggregate
9212        // select list, so it is resolved here rather than by a resolver of its
9213        // own -- the correlation is what makes it a per-parent answer.
9214        let aggregate_of = field
9215            .name()
9216            .strip_suffix("_aggregate")
9217            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list));
9218
9219        if let Some(rel) = aggregate_of {
9220            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9221                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9222            *alias_counter += 1;
9223            let child_alias = format!("a{}", alias_counter);
9224
9225            // The same narrowing an embedded list takes. `where` here decides
9226            // which rows are counted, not which are returned, so it goes on the
9227            // set the aggregate reads rather than on the answer.
9228            let arguments = embed_arguments(field);
9229            let (child_distinct, child_where, child_order, child_limit, child_offset) =
9230                embed_narrowing(
9231                    &arguments,
9232                    &plan,
9233                    schema_cache,
9234                    relationships,
9235                    &rel.target_type,
9236                    &child_alias,
9237                    max_rows,
9238                    param_idx,
9239                    values,
9240                    names,
9241                    caller,
9242                )?;
9243
9244            let child_table =
9245                schema_cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
9246                    &plan.foreign_schema,
9247                    &plan.foreign_table,
9248                ));
9249            let select = nested_aggregate_select(
9250                caller,
9251                field,
9252                &child_alias,
9253                schema_cache,
9254                relationships,
9255                &rel.target_type,
9256                child_table,
9257                max_rows,
9258                alias_counter,
9259                param_idx,
9260                values,
9261                names,
9262            )?;
9263
9264            let call = computed_arguments(
9265                rel,
9266                &plan,
9267                &postrust_sql::escape_ident(parent_alias),
9268                arguments.get("args"),
9269                param_idx,
9270                values,
9271                schema_cache,
9272            )?;
9273            let expression = plan
9274                .aggregate_expression(
9275                    parent_alias,
9276                    &postrust_sql::escape_ident(parent_alias),
9277                    &child_alias,
9278                    &select,
9279                    child_limit,
9280                    child_offset,
9281                    child_where.as_deref(),
9282                    child_order.as_deref(),
9283                    call.as_deref(),
9284                    &child_distinct,
9285                    // A GraphQL body is JSON throughout -- nothing here
9286                    // renders a row as text -- and Hasura keeps the order the
9287                    // fields were selected in.
9288                    postrust_core::EmbedRendering::Verbatim,
9289                )
9290                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9291            expressions.push((field.name().to_string(), expression));
9292            continue;
9293        }
9294
9295        let Some(rel) = available.iter().find(|r| r.name == field.name()) else {
9296            continue;
9297        };
9298
9299        let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9300            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9301
9302        *alias_counter += 1;
9303        let child_alias = format!("e{}", alias_counter);
9304
9305        let nested = build_embed_expressions(
9306            caller,
9307            schema_cache,
9308            relationships,
9309            &rel.target_type,
9310            &child_alias,
9311            field.selection_set(),
9312            max_rows,
9313            alias_counter,
9314            param_idx,
9315            values,
9316            names,
9317        )?;
9318
9319        // The arguments written on the embed itself.
9320        let arguments = embed_arguments(field);
9321        let (child_distinct, child_where, child_order, child_limit, child_offset) =
9322            embed_narrowing(
9323                &arguments,
9324                &plan,
9325                schema_cache,
9326                relationships,
9327                &rel.target_type,
9328                &child_alias,
9329                max_rows,
9330                param_idx,
9331                values,
9332                names,
9333                caller,
9334            )?;
9335
9336        // Leaf fields are columns; anything that resolved to a relationship is
9337        // an expression instead.
9338        let child_relationships = relationships.get(&rel.target_type);
9339        let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
9340            &plan.foreign_schema,
9341            &plan.foreign_table,
9342        );
9343        let child_table = schema_cache.get_table(&child_qi);
9344        let mut parts: Vec<String> = Vec::new();
9345        for sub in field.selection_set() {
9346            let name = sub.name();
9347            let is_relationship = child_relationships
9348                .map(|rels| {
9349                    rels.iter().any(|r| {
9350                        r.name == name || (r.is_list && format!("{}_aggregate", r.name) == name)
9351                    })
9352                })
9353                .unwrap_or(false);
9354            if is_relationship {
9355                continue;
9356            }
9357            // A computed column inside an embed is the same call, against the
9358            // child's alias rather than the table.
9359            let column = child_table
9360                .map(|t| table_column_for(names, t, name))
9361                .unwrap_or(name);
9362            let computed = child_table
9363                .filter(|t| t.get_column(column).is_none())
9364                .and_then(|t| {
9365                    let function = names
9366                        .computed_source(&t.schema, &t.name, name)
9367                        .unwrap_or(name);
9368                    t.get_computed_column(function)
9369                });
9370            match computed {
9371                Some(definition) => parts.push(format!(
9372                    "{} AS {}",
9373                    computed_call(
9374                        definition,
9375                        &format!("{}.*", postrust_sql::escape_ident(&child_alias)),
9376                        &postrust_sql::escape_ident(&child_alias),
9377                    ),
9378                    postrust_sql::escape_ident(name)
9379                )),
9380                // A column the child exposes under another name is selected as
9381                // that name, since the row it lands in is the client's.
9382                None if column != name => parts.push(format!(
9383                    "{} AS {}",
9384                    postrust_sql::escape_ident(column),
9385                    postrust_sql::escape_ident(name)
9386                )),
9387                None => parts.push(postrust_sql::escape_ident(name)),
9388            }
9389        }
9390        if parts.is_empty() && nested.is_empty() {
9391            parts.push(
9392                child_table
9393                    .and_then(|table| rename_projection(table, &child_alias, names))
9394                    .unwrap_or_else(|| format!("{}.*", postrust_sql::escape_ident(&child_alias))),
9395            );
9396        }
9397        for (field_name, expression) in nested {
9398            parts.push(format!(
9399                "{} AS {}",
9400                expression,
9401                postrust_sql::escape_ident(&field_name)
9402            ));
9403        }
9404
9405        let computed_call_arguments = computed_arguments(
9406            rel,
9407            &plan,
9408            &postrust_sql::escape_ident(parent_alias),
9409            arguments.get("args"),
9410            param_idx,
9411            values,
9412            schema_cache,
9413        )?;
9414        let expression = plan
9415            .embed_expression(
9416                parent_alias,
9417                // A computed relationship is correlated by argument rather
9418                // than by a key: the function takes the parent row, and an
9419                // alias names that row.
9420                &postrust_sql::escape_ident(parent_alias),
9421                &child_alias,
9422                &format!("{}{}", child_distinct, parts.join(", ")),
9423                child_limit,
9424                child_offset,
9425                child_where.as_deref(),
9426                child_order.as_deref(),
9427                computed_call_arguments.as_deref(),
9428                postrust_core::EmbedRendering::Verbatim,
9429            )
9430            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9431
9432        expressions.push((rel.name.clone(), expression));
9433    }
9434
9435    Ok(expressions)
9436}
9437
9438/// What embedding a relationship needs, independent of the rows involved.
9439///
9440/// The connection is passed alongside rather than held here: embedding runs on
9441/// the request's transaction, and a shared reference to this struct could not
9442/// hand out the mutable borrow the queries need.
9443struct EmbedContext<'c> {
9444    schema_cache: &'c SchemaCache,
9445    relationships: &'c HashMap<String, Vec<RelationshipField>>,
9446    max_rows: Option<i64>,
9447}
9448
9449/// Embed the relationship fields requested on `rows`.
9450///
9451/// One query per relationship per level, not one per row: the parents' join
9452/// keys are collected and passed as a single array. Recurses so a nested
9453/// selection costs one further query per relationship at each depth.
9454fn embed_relationships<'f>(
9455    conn: &'f mut sqlx::PgConnection,
9456    ctx: &'f EmbedContext<'f>,
9457    type_name: &'f str,
9458    selection: async_graphql::SelectionField<'f>,
9459    rows: &'f mut [serde_json::Value],
9460) -> futures::future::BoxFuture<'f, Result<(), async_graphql::Error>> {
9461    Box::pin(async move {
9462        if rows.is_empty() {
9463            return Ok(());
9464        }
9465
9466        let Some(available) = ctx.relationships.get(type_name) else {
9467            return Ok(());
9468        };
9469
9470        for requested in selection.selection_set() {
9471            let Some(rel) = available.iter().find(|r| r.name == requested.name()) else {
9472                continue;
9473            };
9474
9475            let plan =
9476                postrust_core::embed::EmbedPlan::resolve(&rel.relationship, ctx.schema_cache)
9477                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9478
9479            let keys = postrust_core::embed::parent_keys(rows, &plan.local_column);
9480
9481            // Project only what the selection asked for. A GraphQL selection
9482            // names its leaf fields, so the columns are known before the query
9483            // and an unrequested column need not be read, serialised, sent and
9484            // parsed just to be dropped.
9485            //
9486            // A nested relationship joins on a column of the child row, so that
9487            // column is added even when it was not selected; it is removed again
9488            // when the response is shaped. A selection whose sub-fields cannot
9489            // all be resolved to columns falls back to every column.
9490            let mut child_columns: Vec<String> = Vec::new();
9491            let mut project_everything = false;
9492            let child_relationships = ctx.relationships.get(&rel.target_type);
9493            for field in requested.selection_set() {
9494                let name = field.name();
9495                match child_relationships.and_then(|rels| rels.iter().find(|r| r.name == name)) {
9496                    Some(nested_rel) => {
9497                        match postrust_core::embed::EmbedPlan::resolve(
9498                            &nested_rel.relationship,
9499                            ctx.schema_cache,
9500                        ) {
9501                            Ok(nested_plan) => child_columns.push(nested_plan.local_column),
9502                            Err(_) => project_everything = true,
9503                        }
9504                    }
9505                    None => child_columns.push(name.to_string()),
9506                }
9507            }
9508            if project_everything || child_columns.is_empty() {
9509                child_columns.clear();
9510            }
9511
9512            let mut grouped = if keys.is_empty() {
9513                std::collections::HashMap::new()
9514            } else {
9515                let sql = plan
9516                    .children_grouped_sql(ctx.max_rows, &child_columns)
9517                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9518
9519                let fetched = sqlx::query(&sql)
9520                    .bind(&keys)
9521                    .fetch_all(&mut *conn)
9522                    .await
9523                    .map_err(database_error)?;
9524
9525                // The query returns the join key and a JSON array of that key's
9526                // children, grouped by PostgreSQL rather than row by row here.
9527                use sqlx::Row;
9528                let pairs: Vec<(serde_json::Value, serde_json::Value)> = fetched
9529                    .into_iter()
9530                    .filter_map(|row| {
9531                        Some((
9532                            row.try_get::<serde_json::Value, _>(0).ok()?,
9533                            row.try_get::<serde_json::Value, _>(1).ok()?,
9534                        ))
9535                    })
9536                    .collect();
9537
9538                postrust_core::embed::group_from_aggregated(pairs)
9539            };
9540
9541            // Recurse before attaching, so nested embeds land in the values
9542            // copied onto the parents. One query serves every child row at this
9543            // level, so the rows are flattened for the call and put back
9544            // afterwards; skipped when the selection asks for nothing deeper.
9545            let has_deeper_embed = ctx
9546                .relationships
9547                .get(&rel.target_type)
9548                .map(|rels| {
9549                    requested
9550                        .selection_set()
9551                        .any(|field| rels.iter().any(|r| r.name == field.name()))
9552                })
9553                .unwrap_or(false);
9554
9555            if has_deeper_embed {
9556                let mut order: Vec<(String, usize)> = Vec::with_capacity(grouped.len());
9557                let mut flat: Vec<serde_json::Value> = Vec::new();
9558                for (key, children) in grouped.drain() {
9559                    order.push((key, children.len()));
9560                    flat.extend(children);
9561                }
9562
9563                embed_relationships(&mut *conn, ctx, &rel.target_type, requested, &mut flat)
9564                    .await?;
9565
9566                let mut rest = flat.into_iter();
9567                for (key, count) in order {
9568                    grouped.insert(key, rest.by_ref().take(count).collect());
9569                }
9570            }
9571
9572            for row in rows.iter_mut() {
9573                postrust_core::embed::attach_to_parent(row, &rel.name, &plan, &grouped);
9574            }
9575        }
9576
9577        Ok(())
9578    })
9579}
9580
9581/// Build a `where` document that addresses exactly one row by primary key.
9582///
9583/// Used by the by-PK mutations, which take the key columns as arguments instead
9584/// of a free-form `where`.
9585fn pk_where_from_args(
9586    ctx: &ResolverContext<'_>,
9587    schema_name: &str,
9588    table_name: &str,
9589    pk_columns: &[(String, String)],
9590    names: &crate::names::NameOverrides,
9591) -> Result<serde_json::Value, async_graphql::Error> {
9592    if pk_columns.is_empty() {
9593        return Err(async_graphql::Error::new(format!(
9594            "\"{}\" has no primary key, so it cannot be mutated by key",
9595            table_name
9596        )));
9597    }
9598
9599    // `update_x_by_pk` takes its key as one `pk_columns` object; the others
9600    // take the key columns as arguments of their own. Both spellings are what
9601    // a client was generated against, so both are read here.
9602    let from_object = ctx
9603        .args
9604        .try_get("pk_columns")
9605        .ok()
9606        .map(|v| accessor_to_json(&v));
9607
9608    let mut conditions = serde_json::Map::new();
9609    for (col_name, _) in pk_columns {
9610        // Under the name the key column is exposed as, on both sides: the
9611        // argument the client wrote, and the predicate handed on to the same
9612        // builder every other `where` goes through.
9613        let field = names
9614            .column(schema_name, table_name, col_name)
9615            .unwrap_or(col_name);
9616        let value = match &from_object {
9617            Some(serde_json::Value::Object(map)) => map.get(field).cloned().ok_or_else(|| {
9618                async_graphql::Error::new(format!(
9619                    "pk_columns is missing the key column \"{}\"",
9620                    field
9621                ))
9622            })?,
9623            _ => {
9624                let arg = ctx.args.try_get(field).map_err(|_| {
9625                    async_graphql::Error::new(format!(
9626                        "missing required primary key argument \"{}\"",
9627                        field
9628                    ))
9629                })?;
9630                accessor_to_json(&arg)
9631            }
9632        };
9633        conditions.insert(field.to_string(), serde_json::json!({ "_eq": value }));
9634    }
9635
9636    Ok(serde_json::Value::Object(conditions))
9637}
9638
9639/// GraphQL scalar name to use for a primary key argument of the given
9640/// PostgreSQL type.
9641///
9642/// Falls back to `String` for anything that does not map to a plain scalar --
9643/// a composite or array key cannot be expressed as a single named argument, and
9644/// the value is cast to the column's type in SQL anyway.
9645fn pk_argument_type(pg_type: &str) -> String {
9646    let rendered = crate::types::pg_type_to_graphql(pg_type).to_string();
9647    if rendered.starts_with('[') {
9648        "String".to_string()
9649    } else {
9650        rendered
9651    }
9652}
9653
9654/// The public GraphQL type of a primary-key argument.
9655///
9656/// SQL execution still carries the PostgreSQL type beside the key and uses it
9657/// for casts and binding. This is only the schema-facing type.
9658fn exposed_pk_argument_type(
9659    names: &crate::names::NameOverrides,
9660    schema: &str,
9661    table: &str,
9662    column: &str,
9663    pg_type: &str,
9664) -> String {
9665    names
9666        .column_type(schema, table, column)
9667        .map(|given| given.to_string())
9668        .unwrap_or_else(|| pk_argument_type(pg_type))
9669}
9670
9671/// Convert a GraphQL type string to a TypeRef.
9672fn graphql_type_ref(type_str: &str) -> TypeRef {
9673    // Parse type string like "[Users!]!" or "String" or "Int!"
9674    let is_list = type_str.starts_with('[');
9675    let is_nn = type_str.ends_with('!');
9676
9677    // Strip outer modifiers: first the trailing !, then the brackets
9678    let inner = if is_list {
9679        let stripped = type_str
9680            .trim_end_matches('!') // Remove outer !
9681            .trim_start_matches('[') // Remove [
9682            .trim_end_matches(']'); // Remove ]
9683        stripped
9684    } else {
9685        type_str.trim_end_matches('!')
9686    };
9687
9688    let inner_nn = inner.ends_with('!');
9689    let base_type = inner.trim_end_matches('!');
9690
9691    if is_list {
9692        if is_nn {
9693            if inner_nn {
9694                TypeRef::named_nn_list_nn(base_type)
9695            } else {
9696                TypeRef::named_list_nn(base_type)
9697            }
9698        } else if inner_nn {
9699            TypeRef::named_nn_list(base_type)
9700        } else {
9701            TypeRef::named_list(base_type)
9702        }
9703    } else if is_nn {
9704        TypeRef::named_nn(base_type)
9705    } else {
9706        TypeRef::named(base_type)
9707    }
9708}
9709
9710/// The key serde_json wraps a number in when it is keeping its digits.
9711///
9712/// This crate reads `serde_json` with `arbitrary_precision`, so a PostgreSQL
9713/// `numeric` survives a round trip through JSON with every digit it had. The
9714/// cost is on the way in: a fractional number in a *variable* is deserialized
9715/// into async-graphql's own value type as a one-key object holding the text,
9716/// because the generic path cannot know it is looking at a number. Integers
9717/// are unaffected, which is why this went unnoticed until a shape arrived by
9718/// variable and every coordinate in it read as zero.
9719const NUMBER_AS_TEXT: &str = "$serde_json::private::Number";
9720
9721/// The number an arbitrary-precision wrapper is holding, if that is what this
9722/// object is.
9723fn number_kept_as_text(
9724    map: &serde_json::Map<String, serde_json::Value>,
9725) -> Option<serde_json::Value> {
9726    if map.len() != 1 {
9727        return None;
9728    }
9729    let text = map.get(NUMBER_AS_TEXT)?.as_str()?;
9730    serde_json::from_str::<serde_json::Value>(text)
9731        .ok()
9732        .filter(|value| value.is_number())
9733}
9734
9735/// The same, applied to a whole document that came in as one.
9736fn plain_numbers(value: serde_json::Value) -> serde_json::Value {
9737    match value {
9738        serde_json::Value::Object(map) => match number_kept_as_text(&map) {
9739            Some(number) => number,
9740            None => serde_json::Value::Object(
9741                map.into_iter()
9742                    .map(|(k, v)| (k, plain_numbers(v)))
9743                    .collect(),
9744            ),
9745        },
9746        serde_json::Value::Array(items) => {
9747            serde_json::Value::Array(items.into_iter().map(plain_numbers).collect())
9748        }
9749        other => other,
9750    }
9751}
9752
9753/// Convert ValueAccessor to JSON.
9754fn accessor_to_json(accessor: &ValueAccessor<'_>) -> serde_json::Value {
9755    // Use the deserialize method if available, or convert manually
9756    if accessor.is_null() {
9757        serde_json::Value::Null
9758    } else if let Ok(b) = accessor.boolean() {
9759        serde_json::Value::Bool(b)
9760    } else if let Ok(i) = accessor.i64() {
9761        serde_json::Value::Number(i.into())
9762    } else if let Ok(f) = accessor.f64() {
9763        serde_json::Number::from_f64(f)
9764            .map(serde_json::Value::Number)
9765            .unwrap_or(serde_json::Value::Null)
9766    } else if let Ok(s) = accessor.string() {
9767        serde_json::Value::String(s.to_string())
9768    } else if let Ok(name) = accessor.enum_name() {
9769        // An enum value is not a string to `string()`, and falling through to
9770        // null made every `order_by: {name: asc}` read as an empty direction.
9771        serde_json::Value::String(name.to_string())
9772    } else if let Ok(list) = accessor.list() {
9773        serde_json::Value::Array(list.iter().map(|v| accessor_to_json(&v)).collect())
9774    } else if let Ok(obj) = accessor.object() {
9775        let map: serde_json::Map<String, serde_json::Value> = obj
9776            .iter()
9777            .map(|(k, v)| (k.to_string(), accessor_to_json(&v)))
9778            .collect();
9779        match number_kept_as_text(&map) {
9780            Some(number) => number,
9781            None => serde_json::Value::Object(map),
9782        }
9783    } else {
9784        serde_json::Value::Null
9785    }
9786}
9787
9788/// Convert async-graphql Value to JSON.
9789fn value_to_json(value: &Value) -> serde_json::Value {
9790    match value {
9791        Value::Null => serde_json::Value::Null,
9792        Value::Boolean(b) => serde_json::Value::Bool(*b),
9793        Value::Number(n) => {
9794            if let Some(i) = n.as_i64() {
9795                serde_json::Value::Number(i.into())
9796            } else if let Some(f) = n.as_f64() {
9797                serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
9798            } else {
9799                serde_json::Value::Null
9800            }
9801        }
9802        Value::String(s) => serde_json::Value::String(s.clone()),
9803        Value::List(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
9804        Value::Object(obj) => {
9805            let map: serde_json::Map<String, serde_json::Value> = obj
9806                .iter()
9807                .map(|(k, v)| (k.to_string(), value_to_json(v)))
9808                .collect();
9809            serde_json::Value::Object(map)
9810        }
9811        Value::Binary(b) => serde_json::Value::String(base64::Engine::encode(
9812            &base64::engine::general_purpose::STANDARD,
9813            b,
9814        )),
9815        Value::Enum(e) => serde_json::Value::String(e.to_string()),
9816    }
9817}
9818
9819/// Convert JSON to async-graphql Value.
9820fn json_to_value(json: serde_json::Value) -> Value {
9821    match json {
9822        serde_json::Value::Null => Value::Null,
9823        serde_json::Value::Bool(b) => Value::Boolean(b),
9824        serde_json::Value::Number(n) => {
9825            if let Some(i) = n.as_i64() {
9826                Value::Number(i.into())
9827            } else if let Some(f) = n.as_f64() {
9828                Value::Number(async_graphql::Number::from_f64(f).unwrap())
9829            } else {
9830                Value::Null
9831            }
9832        }
9833        serde_json::Value::String(s) => Value::String(s),
9834        serde_json::Value::Array(arr) => Value::List(arr.into_iter().map(json_to_value).collect()),
9835        serde_json::Value::Object(obj) => {
9836            let map: indexmap::IndexMap<async_graphql::Name, Value> = obj
9837                .into_iter()
9838                .map(|(k, v)| (async_graphql::Name::new(k), json_to_value(v)))
9839                .collect();
9840            Value::Object(map)
9841        }
9842    }
9843}
9844
9845/// Register filter input types.
9846///
9847/// These are currently unreachable: the `filter` and `where` arguments are
9848/// declared as the `JSON` scalar, so no field references these input objects
9849/// and async-graphql prunes them from the published schema (introspecting
9850/// `IntFilterInput` returns null). They are kept as the shape to move to if
9851/// filters become typed per column; until then the operators a filter actually
9852/// supports are the ones `build_where_clause` implements, and it rejects
9853/// anything else rather than ignoring it.
9854#[cfg(test)]
9855mod tests {
9856    use super::*;
9857    use indexmap::IndexMap;
9858    use postrust_core::schema_cache::{Cardinality, Column, Relationship, Table};
9859    use postrust_core::QualifiedIdentifier;
9860    use std::collections::{HashMap, HashSet};
9861
9862    fn entity_key_column(pg_type: &str, explicit_id: bool) -> EntityKeyColumn {
9863        EntityKeyColumn {
9864            field_name: "id".to_string(),
9865            column_name: "id".to_string(),
9866            pg_type: pg_type.to_string(),
9867            explicit_id,
9868        }
9869    }
9870
9871    #[test]
9872    fn federation_integer_keys_reject_strings() {
9873        let column = entity_key_column("int4", false);
9874
9875        assert_eq!(
9876            normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap(),
9877            serde_json::json!(2)
9878        );
9879        let error = normalize_entity_key_value("users", &column, &serde_json::json!("2"))
9880            .expect_err("a GraphQL Int cannot be a string");
9881        assert_eq!(
9882            error.message,
9883            "entity representation for \"users\" has invalid key field \"id\": expected a value compatible with PostgreSQL type \"int4\", found a string"
9884        );
9885    }
9886
9887    #[test]
9888    fn federation_bigint_keys_accept_exact_decimal_strings() {
9889        let column = entity_key_column("int8", false);
9890
9891        assert_eq!(
9892            normalize_entity_key_value("users", &column, &serde_json::json!(7)).unwrap(),
9893            serde_json::json!(7)
9894        );
9895        assert_eq!(
9896            normalize_entity_key_value("users", &column, &serde_json::json!("9007199254740993"))
9897                .unwrap(),
9898            serde_json::json!(9_007_199_254_740_993_i64)
9899        );
9900        assert!(normalize_entity_key_value(
9901            "users",
9902            &column,
9903            &serde_json::json!("9223372036854775808")
9904        )
9905        .is_err());
9906        assert!(normalize_entity_key_value("users", &column, &serde_json::json!("7.5")).is_err());
9907    }
9908
9909    #[test]
9910    fn federation_numeric_keys_accept_exact_decimal_strings() {
9911        let column = entity_key_column("numeric", false);
9912
9913        assert_eq!(
9914            normalize_entity_key_value("users", &column, &serde_json::json!(7.5)).unwrap(),
9915            serde_json::json!("7.5")
9916        );
9917        assert_eq!(
9918            normalize_entity_key_value(
9919                "users",
9920                &column,
9921                &serde_json::json!("9007199254740993.123456789")
9922            )
9923            .unwrap(),
9924            serde_json::json!("9007199254740993.123456789")
9925        );
9926        assert!(
9927            normalize_entity_key_value("users", &column, &serde_json::json!("not-a-number"))
9928                .is_err()
9929        );
9930    }
9931
9932    #[test]
9933    fn federation_integer_backed_ids_have_one_normal_form() {
9934        let column = entity_key_column("int4", true);
9935
9936        let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
9937        let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
9938        assert_eq!(number, serde_json::json!(2));
9939        assert_eq!(string, number);
9940        assert!(normalize_entity_key_value("users", &column, &serde_json::json!("two")).is_err());
9941    }
9942
9943    #[test]
9944    fn federation_text_backed_ids_normalize_integer_input_to_text() {
9945        let column = entity_key_column("text", true);
9946
9947        let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
9948        let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
9949        assert_eq!(number, serde_json::json!("2"));
9950        assert_eq!(string, number);
9951    }
9952
9953    #[test]
9954    fn federation_uuid_backed_ids_are_validated_and_canonicalized() {
9955        let column = entity_key_column("uuid", true);
9956
9957        assert_eq!(
9958            normalize_entity_key_value(
9959                "users",
9960                &column,
9961                &serde_json::json!("550E8400-E29B-41D4-A716-446655440000")
9962            )
9963            .unwrap(),
9964            serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
9965        );
9966        assert!(
9967            normalize_entity_key_value("users", &column, &serde_json::json!("not-a-uuid")).is_err()
9968        );
9969    }
9970
9971    fn create_test_table(name: &str) -> Table {
9972        let mut columns = IndexMap::new();
9973        columns.insert(
9974            "id".into(),
9975            Column {
9976                name: "id".into(),
9977                description: None,
9978                nullable: false,
9979                data_type: "integer".into(),
9980                nominal_type: "int4".into(),
9981                max_len: None,
9982                default: Some("nextval('id_seq')".into()),
9983                enum_values: vec![],
9984                is_pk: true,
9985                position: 1,
9986                always_generated: false,
9987                domain_type: None,
9988            },
9989        );
9990        columns.insert(
9991            "name".into(),
9992            Column {
9993                name: "name".into(),
9994                description: None,
9995                nullable: false,
9996                data_type: "text".into(),
9997                nominal_type: "text".into(),
9998                max_len: None,
9999                default: None,
10000                enum_values: vec![],
10001                is_pk: false,
10002                position: 2,
10003                always_generated: false,
10004                domain_type: None,
10005            },
10006        );
10007
10008        Table {
10009            schema: "public".into(),
10010            name: name.into(),
10011            description: None,
10012            is_view: false,
10013            insertable: true,
10014            updatable: true,
10015            deletable: true,
10016            pk_cols: vec!["id".into()],
10017            unique_constraints: Vec::new(),
10018            columns,
10019            computed_columns: Default::default(),
10020            is_partitioned: false,
10021        }
10022    }
10023
10024    fn create_test_schema_cache() -> SchemaCache {
10025        let mut tables = HashMap::new();
10026        let users = create_test_table("users");
10027        tables.insert(users.qualified_identifier(), users);
10028
10029        SchemaCache {
10030            tables,
10031            relationships: HashMap::new(),
10032            routines: HashMap::new(),
10033            timezones: HashSet::new(),
10034            media_handlers: HashMap::new(),
10035            pg_version: 150000,
10036            representations: Default::default(),
10037        }
10038    }
10039
10040    fn add_test_table(cache: &mut SchemaCache, name: &str) {
10041        let table = create_test_table(name);
10042        cache.tables.insert(table.qualified_identifier(), table);
10043    }
10044
10045    fn add_test_posts_relationship(cache: &mut SchemaCache) {
10046        let users = QualifiedIdentifier::new("public", "users");
10047        cache.relationships.insert(
10048            (users.clone(), "public".into()),
10049            vec![Relationship::ForeignKey {
10050                table: users,
10051                foreign_table: QualifiedIdentifier::new("public", "posts"),
10052                is_self: false,
10053                cardinality: Cardinality::O2M {
10054                    constraint: "posts_user_id_fkey".into(),
10055                    columns: vec![("id".into(), "user_id".into())],
10056                },
10057                table_is_view: false,
10058                foreign_table_is_view: false,
10059                constraint_name: "posts_user_id_fkey".into(),
10060            }],
10061        );
10062    }
10063
10064    // ============================================================================
10065    // Type Reference Tests
10066    // ============================================================================
10067
10068    #[test]
10069    fn test_graphql_type_ref_simple() {
10070        let _type_ref = graphql_type_ref("String");
10071        // TypeRef doesn't implement PartialEq, so we just test it doesn't panic
10072    }
10073
10074    #[test]
10075    fn test_graphql_type_ref_non_null() {
10076        let _type_ref = graphql_type_ref("String!");
10077    }
10078
10079    #[test]
10080    fn test_graphql_type_ref_list() {
10081        let _type_ref = graphql_type_ref("[String]");
10082    }
10083
10084    #[test]
10085    fn test_graphql_type_ref_list_non_null() {
10086        let _type_ref = graphql_type_ref("[String!]!");
10087    }
10088
10089    // ============================================================================
10090    // Value Conversion Tests
10091    // ============================================================================
10092
10093    #[test]
10094    fn test_value_to_json_null() {
10095        let value = Value::Null;
10096        let json = value_to_json(&value);
10097        assert_eq!(json, serde_json::Value::Null);
10098    }
10099
10100    #[test]
10101    fn test_value_to_json_boolean() {
10102        let value = Value::Boolean(true);
10103        let json = value_to_json(&value);
10104        assert_eq!(json, serde_json::Value::Bool(true));
10105    }
10106
10107    #[test]
10108    fn test_value_to_json_number() {
10109        let value = Value::Number(42.into());
10110        let json = value_to_json(&value);
10111        assert_eq!(json, serde_json::json!(42));
10112    }
10113
10114    #[test]
10115    fn test_value_to_json_string() {
10116        let value = Value::String("hello".to_string());
10117        let json = value_to_json(&value);
10118        assert_eq!(json, serde_json::Value::String("hello".to_string()));
10119    }
10120
10121    #[test]
10122    fn test_value_to_json_list() {
10123        let value = Value::List(vec![Value::Number(1.into()), Value::Number(2.into())]);
10124        let json = value_to_json(&value);
10125        assert_eq!(json, serde_json::json!([1, 2]));
10126    }
10127
10128    #[test]
10129    fn test_json_to_value_null() {
10130        let json = serde_json::Value::Null;
10131        let value = json_to_value(json);
10132        assert!(matches!(value, Value::Null));
10133    }
10134
10135    #[test]
10136    fn test_json_to_value_boolean() {
10137        let json = serde_json::Value::Bool(false);
10138        let value = json_to_value(json);
10139        assert!(matches!(value, Value::Boolean(false)));
10140    }
10141
10142    #[test]
10143    fn test_json_to_value_number() {
10144        let json = serde_json::json!(123);
10145        let value = json_to_value(json);
10146        assert!(matches!(value, Value::Number(_)));
10147    }
10148
10149    #[test]
10150    fn test_json_to_value_string() {
10151        let json = serde_json::Value::String("test".to_string());
10152        let value = json_to_value(json);
10153        assert!(matches!(value, Value::String(_)));
10154    }
10155
10156    #[test]
10157    fn test_json_to_value_array() {
10158        let json = serde_json::json!([1, 2, 3]);
10159        let value = json_to_value(json);
10160        assert!(matches!(value, Value::List(_)));
10161    }
10162
10163    #[test]
10164    fn test_json_to_value_object() {
10165        let json = serde_json::json!({"key": "value"});
10166        let value = json_to_value(json);
10167        assert!(matches!(value, Value::Object(_)));
10168    }
10169
10170    // ============================================================================
10171    // Schema Building Tests
10172    // ============================================================================
10173
10174    #[test]
10175    fn test_build_dynamic_schema() {
10176        let cache = create_test_schema_cache();
10177        let config = SchemaConfig::default();
10178        let generated = build_schema(&cache, &config);
10179
10180        let result = build_dynamic_schema(
10181            &generated,
10182            &cache,
10183            None,
10184            None,
10185            Arc::new(Default::default()),
10186            std::time::Duration::from_secs(30),
10187            None,
10188        );
10189        if let Err(ref e) = result {
10190            eprintln!("Schema build error: {:?}", e);
10191        }
10192        assert!(result.is_ok(), "Schema build failed: {:?}", result.err());
10193    }
10194
10195    #[test]
10196    fn a_column_type_override_reaches_fields_and_key_arguments() {
10197        let cache = create_test_schema_cache();
10198        let names = crate::names::NameOverrides::parse(
10199            r#"{"tables": {"public.users": {"column_types": {"id": "ID"}}}}"#,
10200        )
10201        .unwrap();
10202        let config = SchemaConfig {
10203            names: names.clone(),
10204            ..SchemaConfig::default()
10205        };
10206        let generated = build_schema(&cache, &config);
10207        let schema = build_dynamic_schema(
10208            &generated,
10209            &cache,
10210            None,
10211            None,
10212            Arc::new(names),
10213            std::time::Duration::from_secs(30),
10214            None,
10215        )
10216        .expect("schema with an ID override should build");
10217        let sdl = schema.sdl();
10218
10219        let input_body = |name: &str| {
10220            sdl.split_once(&format!("input {name} {{"))
10221                .and_then(|(_, rest)| rest.split_once('}'))
10222                .map(|(body, _)| body.to_string())
10223                .unwrap_or_else(|| panic!("missing input {name}:\n{sdl}"))
10224        };
10225
10226        assert!(sdl.contains("id: ID!"), "object and input fields:\n{sdl}");
10227        assert!(
10228            sdl.contains("users_by_pk(id: ID!): users"),
10229            "query key argument:\n{sdl}"
10230        );
10231        assert!(
10232            sdl.contains("delete_users_by_pk(id: ID!): users"),
10233            "delete key argument:\n{sdl}"
10234        );
10235        assert!(
10236            input_body("users_pk_columns_input").contains("id: ID!"),
10237            "update key input:\n{sdl}"
10238        );
10239        assert!(
10240            input_body("users_bool_exp").contains("id: ID_comparison_exp"),
10241            "comparison input:\n{sdl}"
10242        );
10243        assert!(
10244            input_body("users_insert_input").contains("id: ID"),
10245            "write input:\n{sdl}"
10246        );
10247    }
10248
10249    #[test]
10250    fn a_type_prefix_names_the_complete_dynamic_schema() {
10251        let cache = create_test_schema_cache();
10252        let config = SchemaConfig {
10253            type_prefix: Some("test".into()),
10254            ..SchemaConfig::default()
10255        };
10256        let generated = build_schema(&cache, &config);
10257        let schema = build_dynamic_schema(
10258            &generated,
10259            &cache,
10260            None,
10261            None,
10262            Arc::new(Default::default()),
10263            std::time::Duration::from_secs(30),
10264            None,
10265        )
10266        .expect("a prefixed schema should build");
10267        let sdl = schema.sdl();
10268
10269        assert!(sdl.contains("type test_users "), "object type:\n{sdl}");
10270        assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10271        assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10272        assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10273        assert!(
10274            sdl.contains("test_users_aggregate"),
10275            "aggregate type:\n{sdl}"
10276        );
10277        assert!(sdl.contains("test_insert_users("), "mutation root:\n{sdl}");
10278    }
10279
10280    #[test]
10281    fn federation_is_absent_by_default() {
10282        let cache = create_test_schema_cache();
10283        let generated = build_schema(&cache, &SchemaConfig::default());
10284        let schema = build_dynamic_schema(
10285            &generated,
10286            &cache,
10287            None,
10288            None,
10289            Arc::new(Default::default()),
10290            std::time::Duration::from_secs(30),
10291            None,
10292        )
10293        .expect("a non-federated schema should build");
10294        let sdl = schema.sdl();
10295
10296        assert!(!sdl.contains("_Service"), "service type:\n{sdl}");
10297        assert!(!sdl.contains("_service"), "service field:\n{sdl}");
10298        assert!(!sdl.contains("@key"), "federation key:\n{sdl}");
10299    }
10300
10301    #[test]
10302    fn federation_entity_selections_respect_fragment_type_conditions() {
10303        let document = async_graphql::parser::parse_query(
10304            r#"
10305            query {
10306              _entities(representations: []) {
10307                __typename
10308                ... on authors { id items { title } }
10309                ...ShopFields
10310                ... { common }
10311                ...EntityName
10312              }
10313            }
10314            fragment ShopFields on shops { id items { sku } }
10315            fragment EntityName on _Entity { __typename }
10316            "#,
10317        )
10318        .expect("query parses");
10319        let (_, operation) = document.operations.iter().next().expect("one operation");
10320        let entity = operation
10321            .node
10322            .selection_set
10323            .node
10324            .items
10325            .iter()
10326            .find_map(|selection| match &selection.node {
10327                Selection::Field(field) if field.node.name.node == "_entities" => Some(&field.node),
10328                _ => None,
10329            })
10330            .expect("_entities field");
10331
10332        assert_eq!(
10333            entity_field_applicability(&entity.selection_set.node, &document.fragments, "authors"),
10334            vec![true, true, true, false, false, true, true]
10335        );
10336        assert_eq!(
10337            entity_field_applicability(&entity.selection_set.node, &document.fragments, "shops"),
10338            vec![true, false, false, true, true, true, true]
10339        );
10340    }
10341
10342    #[tokio::test]
10343    async fn federation_entity_lists_allow_null_members() {
10344        let entity =
10345            Object::new("widgets").field(Field::new("id", TypeRef::named_nn(TypeRef::INT), |_| {
10346                FieldFuture::new(async { Ok(Some(Value::from(2))) })
10347            }));
10348        let union = Union::new("_Entity").possible_type("widgets");
10349        let query = Object::new("Query").field(Field::new(
10350            "_entities",
10351            TypeRef::named_list_nn("_Entity"),
10352            |_| {
10353                FieldFuture::new(async {
10354                    Ok(Some(FieldValue::list([
10355                        FieldValue::NULL,
10356                        FieldValue::NULL.with_type("widgets"),
10357                    ])))
10358                })
10359            },
10360        ));
10361        let schema = Schema::build("Query", None, None)
10362            .extension(NullableFederationEntity)
10363            .register(entity)
10364            .register(union)
10365            .register(query)
10366            .finish()
10367            .expect("schema builds");
10368
10369        let response = schema
10370            .execute("{ _entities { ... on widgets { id } } }")
10371            .await;
10372
10373        assert!(response.errors.is_empty(), "{:?}", response.errors);
10374        assert_eq!(
10375            response.data,
10376            async_graphql::value!({ "_entities": [null, { "id": 2 }] })
10377        );
10378    }
10379
10380    #[test]
10381    fn federation_metadata_is_byte_for_byte_inert_when_disabled() {
10382        let cache = create_test_schema_cache();
10383        let sdl_for = |config: SchemaConfig| {
10384            let generated = build_schema(&cache, &config);
10385            build_dynamic_schema(
10386                &generated,
10387                &cache,
10388                None,
10389                None,
10390                Arc::new(config.names.clone()),
10391                std::time::Duration::from_secs(30),
10392                None,
10393            )
10394            .expect("schema should build")
10395            .sdl()
10396        };
10397
10398        let baseline = sdl_for(SchemaConfig {
10399            type_prefix: Some("test".into()),
10400            ..SchemaConfig::default()
10401        });
10402        let with_disabled_federation_metadata = sdl_for(SchemaConfig {
10403            enable_federation: false,
10404            type_prefix: Some("test".into()),
10405            names: crate::names::NameOverrides::parse(
10406                r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10407            )
10408            .expect("metadata"),
10409            ..SchemaConfig::default()
10410        });
10411
10412        assert_eq!(with_disabled_federation_metadata, baseline);
10413        assert!(
10414            with_disabled_federation_metadata.contains("type test_users "),
10415            "prefixed object type remains when federation is disabled:\n{with_disabled_federation_metadata}"
10416        );
10417        assert!(
10418            !with_disabled_federation_metadata.contains("_Service"),
10419            "service type:\n{with_disabled_federation_metadata}"
10420        );
10421        assert!(
10422            !with_disabled_federation_metadata.contains("@key"),
10423            "federation key:\n{with_disabled_federation_metadata}"
10424        );
10425    }
10426
10427    #[test]
10428    fn shared_entities_make_relationship_fields_shareable() {
10429        let federation_sdl = |shared: bool| {
10430            let mut cache = create_test_schema_cache();
10431            add_test_table(&mut cache, "posts");
10432            add_test_posts_relationship(&mut cache);
10433            let names = match shared {
10434                true => crate::names::NameOverrides::parse(
10435                    r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10436                )
10437                .expect("metadata"),
10438                false => crate::names::NameOverrides::default(),
10439            };
10440            let config = SchemaConfig {
10441                enable_federation: true,
10442                type_prefix: Some("test".into()),
10443                names: names.clone(),
10444                ..SchemaConfig::default()
10445            };
10446            let generated = build_schema(&cache, &config);
10447            build_dynamic_schema(
10448                &generated,
10449                &cache,
10450                None,
10451                None,
10452                Arc::new(names),
10453                std::time::Duration::from_secs(30),
10454                None,
10455            )
10456            .expect("federated schema should build")
10457            .sdl_with_options(async_graphql::SDLExportOptions::new().federation())
10458        };
10459
10460        let shared = federation_sdl(true);
10461        assert!(
10462            shared.contains("): [test_posts!]! @shareable"),
10463            "shared relationship field:\n{shared}"
10464        );
10465        assert!(
10466            shared.contains("): test_posts_aggregate! @shareable"),
10467            "shared relationship aggregate field:\n{shared}"
10468        );
10469
10470        let not_shared = federation_sdl(false);
10471        assert!(
10472            not_shared.contains("): [test_posts!]!"),
10473            "non-shared relationship field:\n{not_shared}"
10474        );
10475        assert!(
10476            !not_shared.contains("): [test_posts!]! @shareable"),
10477            "non-shared relationship field must not be shareable:\n{not_shared}"
10478        );
10479        assert!(
10480            not_shared.contains("): test_posts_aggregate!"),
10481            "non-shared relationship aggregate field:\n{not_shared}"
10482        );
10483        assert!(
10484            !not_shared.contains("): test_posts_aggregate! @shareable"),
10485            "non-shared relationship aggregate must not be shareable:\n{not_shared}"
10486        );
10487    }
10488
10489    #[tokio::test]
10490    async fn federation_exposes_service_sdl_and_entity_keys() {
10491        let mut cache = create_test_schema_cache();
10492        add_test_table(&mut cache, "posts");
10493        let config = SchemaConfig {
10494            enable_federation: true,
10495            type_prefix: Some("test".into()),
10496            names: crate::names::NameOverrides::parse(
10497                r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10498            )
10499            .expect("metadata"),
10500            ..SchemaConfig::default()
10501        };
10502        let generated = build_schema(&cache, &config);
10503        let schema = build_dynamic_schema(
10504            &generated,
10505            &cache,
10506            None,
10507            None,
10508            Arc::new(Default::default()),
10509            std::time::Duration::from_secs(30),
10510            None,
10511        )
10512        .expect("a federated schema should build");
10513        let sdl = schema.sdl();
10514        let federation_sdl =
10515            schema.sdl_with_options(async_graphql::SDLExportOptions::new().federation());
10516
10517        assert!(sdl.contains("_Service"), "service type:\n{sdl}");
10518        assert!(sdl.contains("_service"), "service field:\n{sdl}");
10519        assert!(
10520            federation_sdl.contains("type users @key(fields: \"id\")"),
10521            "users key:\n{federation_sdl}"
10522        );
10523        assert!(
10524            federation_sdl.contains("@shareable"),
10525            "shared field directive:\n{federation_sdl}"
10526        );
10527        assert!(sdl.contains("type users "), "shared object type:\n{sdl}");
10528        assert!(
10529            !sdl.contains("type test_users "),
10530            "prefixed shared object type:\n{sdl}"
10531        );
10532        assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10533        assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10534        assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10535        assert!(
10536            !sdl.contains("input users_bool_exp "),
10537            "unprefixed derived input:\n{sdl}"
10538        );
10539        assert!(
10540            sdl.contains("test_users_aggregate"),
10541            "aggregate type:\n{sdl}"
10542        );
10543        assert!(
10544            !sdl.contains("type users_aggregate "),
10545            "unprefixed aggregate type:\n{sdl}"
10546        );
10547        assert!(
10548            sdl.contains("test_users_mutation_response"),
10549            "mutation response type:\n{sdl}"
10550        );
10551        assert!(
10552            !sdl.contains("type users_mutation_response "),
10553            "unprefixed mutation response type:\n{sdl}"
10554        );
10555        assert!(
10556            sdl.contains("type test_posts {"),
10557            "non-shared object type remains prefixed:\n{sdl}"
10558        );
10559        assert!(
10560            !sdl.contains("type posts {"),
10561            "non-shared object type must not become unprefixed:\n{sdl}"
10562        );
10563        assert!(sdl.contains("test_posts("), "prefixed posts root:\n{sdl}");
10564        assert!(
10565            sdl.contains("test_posts_bool_exp"),
10566            "prefixed posts bool_exp:\n{sdl}"
10567        );
10568
10569        let response = schema.execute("{ _service { sdl } }").await;
10570        assert!(
10571            response.errors.is_empty(),
10572            "service query errors: {:?}",
10573            response.errors
10574        );
10575        let service = response.data.into_json().expect("service response JSON");
10576        let service_sdl = service
10577            .get("_service")
10578            .and_then(|value| value.get("sdl"))
10579            .and_then(serde_json::Value::as_str)
10580            .expect("service SDL string");
10581        assert!(
10582            service_sdl.contains("type Query"),
10583            "federation root type:\n{service_sdl}"
10584        );
10585        assert!(
10586            service_sdl.contains("test_users("),
10587            "query fields are on the federation root:\n{service_sdl}"
10588        );
10589        assert!(
10590            service_sdl.contains("test_posts("),
10591            "non-shared query fields are on the federation root:\n{service_sdl}"
10592        );
10593        assert!(
10594            !service_sdl.contains("type query_root"),
10595            "Apollo composition treats query_root as an ordinary object without a schema definition:\n{service_sdl}"
10596        );
10597        assert!(
10598            service_sdl.contains("type users @key(fields: \"id\")"),
10599            "{service_sdl}"
10600        );
10601        assert!(
10602            service_sdl.contains("type test_posts @key(fields: \"id\")"),
10603            "{service_sdl}"
10604        );
10605    }
10606
10607    /// `_exists` becomes a subselect over the table it names, correlated with
10608    /// nothing -- which is the difference between it and a relationship.
10609    #[test]
10610    fn exists_is_a_subselect_over_another_table() {
10611        let cache = create_test_schema_cache();
10612        let names = crate::names::NameOverrides::default();
10613        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10614        let scope = WhereScope::table("public", "users", "users", &names)
10615            .with_resolution(&cache, &relationships);
10616
10617        let mut param_idx = 1usize;
10618        let mut values: Vec<serde_json::Value> = Vec::new();
10619        let mut aliases = 0usize;
10620        let sql = build_condition(
10621            &serde_json::json!({
10622                "_exists": {"_table": "users", "_where": {"name": {"_eq": "Ann"}}}
10623            }),
10624            &scope,
10625            &mut param_idx,
10626            &mut values,
10627            &mut aliases,
10628        )
10629        .expect("compiles")
10630        .expect("constrains something");
10631
10632        assert_eq!(
10633            sql,
10634            "EXISTS (SELECT 1 FROM \"public\".\"users\" AS \"pgrst_exists_1\" \
10635             WHERE \"pgrst_exists_1\".\"name\" = $1::text)"
10636        );
10637        assert_eq!(values, vec![serde_json::json!("Ann")]);
10638    }
10639
10640    /// A `_table` nobody has is refused rather than written into the query.
10641    #[test]
10642    fn exists_names_a_table_the_server_has() {
10643        let cache = create_test_schema_cache();
10644        let names = crate::names::NameOverrides::default();
10645        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10646        let scope = WhereScope::table("public", "users", "users", &names)
10647            .with_resolution(&cache, &relationships);
10648        let error = build_condition(
10649            &serde_json::json!({"_exists": {"_table": "nowhere", "_where": {}}}),
10650            &scope,
10651            &mut 1,
10652            &mut Vec::new(),
10653            &mut 0,
10654        )
10655        .expect_err("a table that is not there is an error");
10656        assert!(
10657            error.message.contains("public.nowhere"),
10658            "{}",
10659            error.message
10660        );
10661    }
10662
10663    /// A role granted "how many" and not "which" gets the count and no rows.
10664    ///
10665    /// A select permission naming no columns, with `allow_aggregations`, is
10666    /// Hasura's way of writing that. The table has an aggregate root and no
10667    /// row type, so `nodes` is not there to be a list of one -- and `count`
10668    /// takes no `columns`, because there are none to name.
10669    #[test]
10670    fn a_table_a_role_may_count_and_not_read_has_a_count_and_no_rows() {
10671        let cache = create_test_schema_cache();
10672        let names = crate::names::NameOverrides::parse(
10673            r#"{"tables": {"public.users": {"permissions":
10674                 {"counter": {"select": {"columns": [], "filter": {},
10675                                         "allow_aggregations": true}}}}}}"#,
10676        )
10677        .unwrap();
10678        let view = crate::role::cache_for_role(&cache, &names, "counter", false);
10679        assert!(!view.tables.is_empty(), "the table is there to be counted");
10680
10681        let config = SchemaConfig {
10682            names: names.clone(),
10683            role: Some("counter".to_string()),
10684            ..SchemaConfig::default()
10685        };
10686        let generated = build_schema(&view, &config);
10687        let schema = build_dynamic_schema(
10688            &generated,
10689            &view,
10690            None,
10691            None,
10692            Arc::new(names),
10693            std::time::Duration::from_secs(30),
10694            Some("counter"),
10695        )
10696        .expect("a role that may only count still has a buildable schema");
10697        let sdl = schema.sdl();
10698
10699        assert!(
10700            sdl.contains("users_aggregate"),
10701            "the count is there:\n{}",
10702            sdl
10703        );
10704        assert!(sdl.contains("count: Int!"), "and it is a count:\n{}", sdl);
10705        // No row type, so nothing that would answer with one.
10706        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10707        assert!(
10708            !sdl.contains("nodes"),
10709            "and no rows beside the numbers:\n{}",
10710            sdl
10711        );
10712        assert!(
10713            !sdl.contains("users_select_column"),
10714            "no column enum to order by or count over:\n{}",
10715            sdl
10716        );
10717        // The functions that read column data are absent with the columns.
10718        assert!(!sdl.contains("users_max_fields"), "no max:\n{}", sdl);
10719    }
10720
10721    /// A role granted `insert` and no `select` gets the write and no type.
10722    ///
10723    /// Hasura's own corpus does this eight times over. What has to hold is
10724    /// that the schema still builds: `insert_users` is there, `users` is not,
10725    /// and the response the insert answers with has no `returning` to name a
10726    /// type that does not exist.
10727    #[test]
10728    fn a_table_a_role_may_only_write_gets_the_write_and_no_type() {
10729        let cache = create_test_schema_cache();
10730        let names = crate::names::NameOverrides::parse(
10731            r#"{"tables": {"public.users": {"permissions":
10732                 {"writer": {"insert": {"columns": "*", "check": {}}}}}}}"#,
10733        )
10734        .unwrap();
10735        let view = crate::role::cache_for_role(&cache, &names, "writer", false);
10736        let config = SchemaConfig {
10737            names: names.clone(),
10738            role: Some("writer".to_string()),
10739            ..SchemaConfig::default()
10740        };
10741        let generated = build_schema(&view, &config);
10742
10743        let schema = build_dynamic_schema(
10744            &generated,
10745            &view,
10746            None,
10747            None,
10748            Arc::new(names),
10749            std::time::Duration::from_secs(30),
10750            Some("writer"),
10751        )
10752        .expect("a role that may only write still has a buildable schema");
10753        let sdl = schema.sdl();
10754
10755        assert!(
10756            sdl.contains("insert_users("),
10757            "the write is there:\n{}",
10758            sdl
10759        );
10760        assert!(
10761            sdl.contains("users_insert_input"),
10762            "and its input:\n{}",
10763            sdl
10764        );
10765        // No row type, and so none of the three fields that answer with one.
10766        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10767        assert!(!sdl.contains("insert_users_one"), "no insert_one:\n{}", sdl);
10768        assert!(
10769            !sdl.contains("returning"),
10770            "and nothing to return:\n{}",
10771            sdl
10772        );
10773        assert!(
10774            sdl.contains("users_mutation_response"),
10775            "the count is still answered:\n{}",
10776            sdl
10777        );
10778        assert!(
10779            !sdl.contains("_mutation_response_mutation_response"),
10780            "and no type is named twice over:\n{}",
10781            sdl
10782        );
10783    }
10784
10785    /// A cache with one VOLATILE function returning rows of `users`, which is
10786    /// the shape `add_to_score` has in Hasura's corpus.
10787    fn cache_with_a_mutation_function() -> SchemaCache {
10788        use postrust_core::schema_cache::{FuncVolatility, RetType, Routine};
10789
10790        let mut cache = create_test_schema_cache();
10791        let routine = Routine {
10792            schema: "public".into(),
10793            name: "add_to_score".into(),
10794            description: None,
10795            params: Vec::new(),
10796            return_type: RetType::SetOf("users".into()),
10797            returns_composite: true,
10798            volatility: FuncVolatility::Volatile,
10799            has_variadic: false,
10800            isolation_level: None,
10801            settings: Vec::new(),
10802            is_procedure: false,
10803            media_type: None,
10804            media_base_type: None,
10805            output_columns: Vec::new(),
10806        };
10807        cache
10808            .routines
10809            .insert(routine.qualified_identifier(), vec![routine]);
10810        cache
10811    }
10812
10813    /// A function with side effects is not granted by permission to read the
10814    /// table it returns. Hasura infers a query function's permission from
10815    /// that select and infers nothing for a mutation, so a role never named
10816    /// by a function permission has no mutation root at all.
10817    #[test]
10818    fn a_mutation_function_needs_the_role_to_be_named() {
10819        let cache = cache_with_a_mutation_function();
10820        let withheld = crate::names::NameOverrides::parse(
10821            r#"{"tables": {"public.users": {"permissions":
10822                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10823                "functions": {"public.add_to_score": {"exposed_as": "mutation"}}}"#,
10824        )
10825        .unwrap();
10826        let granted = crate::names::NameOverrides::parse(
10827            r#"{"tables": {"public.users": {"permissions":
10828                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10829                "functions": {"public.add_to_score":
10830                  {"exposed_as": "mutation", "roles": ["reader"]}}}"#,
10831        )
10832        .unwrap();
10833
10834        let sdl_for = |names: &crate::names::NameOverrides| {
10835            let view = crate::role::cache_for_role(&cache, names, "reader", false);
10836            let config = SchemaConfig {
10837                names: names.clone(),
10838                role: Some("reader".to_string()),
10839                ..SchemaConfig::default()
10840            };
10841            let generated = build_schema(&view, &config);
10842            build_dynamic_schema(
10843                &generated,
10844                &view,
10845                None,
10846                None,
10847                Arc::new(names.clone()),
10848                std::time::Duration::from_secs(30),
10849                Some("reader"),
10850            )
10851            .expect("schema builds")
10852            .sdl()
10853        };
10854
10855        let without = sdl_for(&withheld);
10856        assert!(
10857            !without.contains("add_to_score"),
10858            "not granted, so not there:\n{}",
10859            without
10860        );
10861        // And with no mutation of any kind left, there is no mutation root --
10862        // which is what `no mutations exist` is answered from.
10863        assert!(
10864            !without.contains("mutation: mutation_root"),
10865            "and no mutation root at all:\n{}",
10866            without
10867        );
10868
10869        let with = sdl_for(&granted);
10870        assert!(
10871            with.contains("add_to_score"),
10872            "named by a function permission, so it is:\n{}",
10873            with
10874        );
10875    }
10876
10877    /// An unrecognised role is a role that was granted nothing, not an error.
10878    #[test]
10879    fn a_role_the_document_never_names_is_granted_nothing() {
10880        let cache = Arc::new(create_test_schema_cache());
10881        let config = SchemaConfig {
10882            names: crate::names::NameOverrides::parse(
10883                r#"{"tables": {"public.users": {"permissions":
10884                     {"reader": {"select": {"columns": "*", "filter": {}}}}}}}"#,
10885            )
10886            .unwrap(),
10887            ..SchemaConfig::default()
10888        };
10889        let empty = build_ungranted_schema(&cache, &config).expect("it builds");
10890        let sdl = empty.sdl();
10891        assert!(
10892            sdl.contains("no_queries_available"),
10893            "nothing to read:\n{}",
10894            sdl
10895        );
10896        assert!(!sdl.contains("type users "), "and no table:\n{}", sdl);
10897        // A document with no permissions at all has no unrecognised roles.
10898        assert!(build_ungranted_schema(&cache, &SchemaConfig::default()).is_none());
10899    }
10900
10901    /// A column the role may write and not read is in the input and not in the
10902    /// type. The two column sets are the point: one schema cache, two answers.
10903    #[test]
10904    fn a_column_a_role_may_set_without_seeing_is_in_the_input_alone() {
10905        let cache = create_test_schema_cache();
10906        let names = crate::names::NameOverrides::parse(
10907            r#"{"tables": {"public.users": {"permissions":
10908                 {"user": {"select": {"columns": ["id"], "filter": {}},
10909                           "insert": {"columns": "*", "check": {}}}}}}}"#,
10910        )
10911        .unwrap();
10912        let view = crate::role::cache_for_role(&cache, &names, "user", false);
10913        let config = SchemaConfig {
10914            names: names.clone(),
10915            role: Some("user".to_string()),
10916            ..SchemaConfig::default()
10917        };
10918        let generated = build_schema(&view, &config);
10919        let object = &generated.object_types["users"];
10920        assert_eq!(
10921            object
10922                .fields
10923                .iter()
10924                .map(|f| f.name.as_str())
10925                .collect::<Vec<_>>(),
10926            vec!["id"],
10927            "the type shows what may be read"
10928        );
10929        assert!(
10930            object.writable_fields.iter().any(|f| f.name == "name"),
10931            "and the input keeps what may be written"
10932        );
10933
10934        let schema = build_dynamic_schema(
10935            &generated,
10936            &view,
10937            None,
10938            None,
10939            Arc::new(names),
10940            std::time::Duration::from_secs(30),
10941            Some("user"),
10942        )
10943        .expect("schema builds");
10944        let sdl = schema.sdl();
10945        let input = sdl
10946            .split("input users_insert_input")
10947            .nth(1)
10948            .and_then(|rest| rest.split('}').next())
10949            .expect("the insert input is registered");
10950        assert!(input.contains("name:"), "settable without being readable");
10951    }
10952
10953    /// A column PostgreSQL generates always is in the row type and in no
10954    /// write input. It is not a permission -- no role may name it -- so the
10955    /// refusal belongs to the schema rather than to the database.
10956    #[test]
10957    fn a_generated_column_is_readable_and_not_writable() {
10958        let mut cache = create_test_schema_cache();
10959        cache
10960            .tables
10961            .get_mut(&postrust_core::api_request::QualifiedIdentifier::new(
10962                "public", "users",
10963            ))
10964            .expect("the fixture has users")
10965            .columns
10966            .get_mut("id")
10967            .expect("with an id")
10968            .always_generated = true;
10969
10970        let names = crate::names::NameOverrides::default();
10971        let config = SchemaConfig {
10972            names: names.clone(),
10973            ..SchemaConfig::default()
10974        };
10975        let generated = build_schema(&cache, &config);
10976        let schema = build_dynamic_schema(
10977            &generated,
10978            &cache,
10979            None,
10980            None,
10981            Arc::new(names),
10982            std::time::Duration::from_secs(30),
10983            None,
10984        )
10985        .expect("schema builds");
10986        let sdl = schema.sdl();
10987
10988        let block = |header: &str| {
10989            sdl.split(header)
10990                .nth(1)
10991                .and_then(|rest| rest.split('}').next())
10992                .unwrap_or_else(|| panic!("{} is registered:\n{}", header, sdl))
10993                .to_string()
10994        };
10995        assert!(
10996            block("type users ").contains("id:"),
10997            "still readable:\n{}",
10998            sdl
10999        );
11000        for header in ["input users_insert_input", "input users_set_input"] {
11001            assert!(
11002                !block(header).contains("id:"),
11003                "{} may not name it:\n{}",
11004                header,
11005                block(header)
11006            );
11007        }
11008        // The table's only number was the generated column, so there is
11009        // nothing left to add to and no type for adding to it.
11010        assert!(
11011            !sdl.contains("users_inc_input"),
11012            "and nothing is left to increment:\n{}",
11013            sdl
11014        );
11015        assert!(
11016            block("input users_insert_input").contains("name:"),
11017            "and the rest of the table is untouched",
11018        );
11019    }
11020
11021    #[test]
11022    fn test_create_object_type() {
11023        let table = create_test_table("users");
11024        let obj = TableObjectType::from_table(&table);
11025        let _gql_obj = create_object_type(&obj, &[], None);
11026    }
11027
11028    #[test]
11029    fn test_create_query_type() {
11030        let cache = create_test_schema_cache();
11031        let config = SchemaConfig::default();
11032        let generated = build_schema(&cache, &config);
11033
11034        let _query = create_query_type(
11035            &generated,
11036            "query_root",
11037            None,
11038            Arc::new(HashMap::new()),
11039            Arc::new(Default::default()),
11040        );
11041    }
11042
11043    #[test]
11044    fn test_create_mutation_type() {
11045        let cache = create_test_schema_cache();
11046        let config = SchemaConfig::default();
11047        let generated = build_schema(&cache, &config);
11048
11049        let _mutation = create_mutation_type(
11050            &generated,
11051            "mutation_root",
11052            Arc::new(HashMap::new()),
11053            Arc::new(HashMap::new()),
11054            Arc::new(Default::default()),
11055            None,
11056            None,
11057        );
11058    }
11059
11060    // ============================================================================
11061    // Scalar Tests
11062    // ============================================================================
11063
11064    #[test]
11065    fn a_scalar_is_named_the_way_a_client_declares_it() {
11066        use crate::types::{pg_type_to_graphql, GraphQLType};
11067
11068        // These names appear in the client's own queries -- `query ($x:
11069        // jsonb!)` names a type that has to exist under exactly that
11070        // spelling -- so they are asserted rather than left to the Display
11071        // impl.
11072        assert_eq!(pg_type_to_graphql("jsonb").to_string(), "jsonb");
11073        assert_eq!(pg_type_to_graphql("json").to_string(), "json");
11074        assert_eq!(pg_type_to_graphql("int8").to_string(), "bigint");
11075        assert_eq!(pg_type_to_graphql("numeric").to_string(), "numeric");
11076        assert_eq!(pg_type_to_graphql("timestamptz").to_string(), "timestamptz");
11077        assert_eq!(pg_type_to_graphql("timestamp").to_string(), "timestamp");
11078        assert_eq!(pg_type_to_graphql("uuid").to_string(), "uuid");
11079
11080        // A type this server knows nothing about keeps its own name, which is
11081        // how a PostGIS column and a database enum both become usable.
11082        assert_eq!(pg_type_to_graphql("geometry").to_string(), "geometry");
11083        assert_eq!(
11084            pg_type_to_graphql("colors_enum"),
11085            GraphQLType::Custom("colors_enum".to_string())
11086        );
11087
11088        // And the leaf of an array is what gets registered, not the array.
11089        assert_eq!(
11090            leaf_scalar_name(&pg_type_to_graphql("_text")),
11091            "String".to_string()
11092        );
11093    }
11094
11095    // ============================================================================
11096    // Filter Input Type Tests
11097    // ============================================================================
11098
11099    #[test]
11100    fn every_table_gets_a_boolean_expression() {
11101        let cache = create_test_schema_cache();
11102        let config = SchemaConfig::default();
11103        let generated = build_schema(&cache, &config);
11104
11105        let (inputs, _) = crate::input::bool_exp::build_inputs(
11106            &generated.object_types,
11107            &generated.relationship_fields,
11108        );
11109        let names: HashSet<String> = inputs.iter().map(|i| i.type_name().to_string()).collect();
11110
11111        for table in generated.object_types.keys() {
11112            let expected = format!("{}_bool_exp", table);
11113            assert!(
11114                names.contains(&expected),
11115                "no {} among {:?}",
11116                expected,
11117                names
11118            );
11119        }
11120        // The scalars the fixture's columns use.
11121        assert!(names.contains("String_comparison_exp"));
11122        assert!(names.contains("Int_comparison_exp"));
11123    }
11124
11125    #[test]
11126    fn a_schema_carrying_the_boolean_expressions_still_builds() {
11127        let cache = create_test_schema_cache();
11128        let config = SchemaConfig::default();
11129        let generated = build_schema(&cache, &config);
11130
11131        let schema = build_dynamic_schema(
11132            &generated,
11133            &cache,
11134            None,
11135            None,
11136            Arc::new(Default::default()),
11137            std::time::Duration::from_secs(30),
11138            None,
11139        );
11140        assert!(schema.is_ok(), "{:?}", schema.err());
11141
11142        let sdl = schema.unwrap().sdl();
11143        assert!(
11144            sdl.contains("_bool_exp"),
11145            "no boolean expressions in:\n{}",
11146            sdl
11147        );
11148        assert!(sdl.contains("_and"), "no _and in the generated expressions");
11149    }
11150
11151    // ============================================================================
11152    // Subscription Tests
11153    // ============================================================================
11154
11155    #[test]
11156    fn test_build_schema_with_subscriptions() {
11157        let cache = create_test_schema_cache();
11158        let config = SchemaConfig {
11159            enable_subscriptions: true,
11160            ..SchemaConfig::default()
11161        };
11162        let generated = build_schema(&cache, &config);
11163
11164        // Generate subscription fields
11165        let sub_fields = generate_subscription_fields(&cache, &generated);
11166        assert!(!sub_fields.is_empty(), "Should have subscription fields");
11167
11168        // Build schema with subscriptions
11169        let result = build_dynamic_schema(
11170            &generated,
11171            &cache,
11172            Some(&sub_fields),
11173            None,
11174            Arc::new(Default::default()),
11175            std::time::Duration::from_secs(30),
11176            None,
11177        );
11178        assert!(result.is_ok(), "Schema with subscriptions should build");
11179    }
11180
11181    #[test]
11182    fn test_subscription_field_generation() {
11183        let cache = create_test_schema_cache();
11184        let config = SchemaConfig::default();
11185        let generated = build_schema(&cache, &config);
11186
11187        let fields = generate_subscription_fields(&cache, &generated);
11188
11189        // Should have one subscription field for the users table
11190        assert_eq!(fields.len(), 1);
11191        assert_eq!(fields[0].name, "users");
11192        assert_eq!(fields[0].table_name, "users");
11193        assert_eq!(fields[0].channel_name(), "postrust_public_users");
11194    }
11195
11196    #[test]
11197    fn test_create_subscription_type() {
11198        let cache = create_test_schema_cache();
11199        let config = SchemaConfig::default();
11200        let generated = build_schema(&cache, &config);
11201        // A live query per queryable root: the subscription root mirrors the
11202        // query root, which is the whole contract.
11203        let _subscription = create_subscription_type(
11204            &generated,
11205            "subscription_root",
11206            None,
11207            Arc::new(Default::default()),
11208            Arc::new(Default::default()),
11209            std::time::Duration::from_secs(30),
11210        );
11211    }
11212}
11213
11214/// One step of a JSON path: a key of an object or an index of an array.
11215#[derive(Debug, PartialEq, Eq)]
11216enum PathStep {
11217    Key(String),
11218    Index(usize),
11219}
11220
11221/// Read one part of a document, by the path spelling Hasura accepts.
11222///
11223/// `column(path: "objs[0]['你好']")` -- an optional leading `$`, then keys
11224/// written bare or after a dot, and indices or quoted keys written in
11225/// brackets. A step that names something the document does not have is null,
11226/// which is what `#>` answers to the same path.
11227fn walk_json_path(value: &Value, path: &str) -> Result<Value, async_graphql::Error> {
11228    let mut at = value;
11229    for step in parse_json_path(path)? {
11230        let next = match (&step, at) {
11231            (PathStep::Key(key), Value::Object(map)) => map.get(key.as_str()),
11232            (PathStep::Index(index), Value::List(items)) => items.get(*index),
11233            // An index of an object or a key of an array reads nothing, and so
11234            // does anything asked of a scalar.
11235            _ => None,
11236        };
11237        match next {
11238            Some(next) => at = next,
11239            None => return Ok(Value::Null),
11240        }
11241    }
11242    Ok(at.clone())
11243}
11244
11245/// A path whose bracket never closes: written wrong rather than written oddly.
11246fn unclosed_bracket(path: &str) -> async_graphql::Error {
11247    let message = format!("\"{}\" is not a JSON path: a bracket is never closed", path);
11248    validation_error(&message)
11249}
11250
11251/// Parse the path spelling Hasura accepts into its steps.
11252///
11253/// Deliberately tolerant about separators, because the corpus writes the same
11254/// path four ways: `objs[0]["x"]`, `objs[0].["x"]`, `objs.[0].["x"]` and
11255/// `.objs[0]['x']` all name one thing. What is not tolerated is a bracket that
11256/// never closes, which is a path the client got wrong rather than a spelling.
11257fn parse_json_path(path: &str) -> Result<Vec<PathStep>, async_graphql::Error> {
11258    let mut steps = Vec::new();
11259    let mut chars = path.chars().peekable();
11260    // `$` is the document itself, and naming it is optional.
11261    if chars.peek() == Some(&'$') {
11262        chars.next();
11263    }
11264    while let Some(&c) = chars.peek() {
11265        match c {
11266            // A separator carries no step of its own.
11267            '.' => {
11268                chars.next();
11269            }
11270            '[' => {
11271                chars.next();
11272                let quote = match chars.peek() {
11273                    Some(&q @ ('\'' | '"')) => {
11274                        chars.next();
11275                        Some(q)
11276                    }
11277                    _ => None,
11278                };
11279                let mut inner = String::new();
11280                loop {
11281                    let Some(c) = chars.next() else {
11282                        return Err(unclosed_bracket(path));
11283                    };
11284                    match (quote, c) {
11285                        (Some(quote), c) if c == quote => break,
11286                        (None, ']') => break,
11287                        _ => inner.push(c),
11288                    }
11289                }
11290                // A quoted key's closing bracket, which the quote ended before.
11291                if quote.is_some() {
11292                    match chars.next() {
11293                        Some(']') => {}
11294                        _ => return Err(unclosed_bracket(path)),
11295                    }
11296                }
11297                // An unquoted number is an index; anything else is a key,
11298                // since an object may be keyed by a word as well as a digit.
11299                match (quote, inner.parse::<usize>()) {
11300                    (None, Ok(index)) => steps.push(PathStep::Index(index)),
11301                    _ => steps.push(PathStep::Key(inner)),
11302                }
11303            }
11304            _ => {
11305                let mut key = String::new();
11306                while let Some(&c) = chars.peek() {
11307                    if matches!(c, '.' | '[') {
11308                        break;
11309                    }
11310                    key.push(c);
11311                    chars.next();
11312                }
11313                steps.push(PathStep::Key(key));
11314            }
11315        }
11316    }
11317    Ok(steps)
11318}
11319
11320#[cfg(test)]
11321mod json_path_tests {
11322    use super::*;
11323
11324    fn steps(path: &str) -> Vec<PathStep> {
11325        parse_json_path(path).expect("the path parses")
11326    }
11327
11328    fn key(name: &str) -> PathStep {
11329        PathStep::Key(name.to_string())
11330    }
11331
11332    #[test]
11333    fn the_document_itself_has_no_steps() {
11334        assert_eq!(steps("$"), vec![]);
11335        assert_eq!(steps(""), vec![]);
11336    }
11337
11338    #[test]
11339    fn a_key_is_written_bare_or_after_a_dot() {
11340        assert_eq!(steps("a"), vec![key("a")]);
11341        assert_eq!(steps(".obj.c1"), vec![key("obj"), key("c1")]);
11342        assert_eq!(steps("._underscore"), vec![key("_underscore")]);
11343    }
11344
11345    #[test]
11346    fn a_bracket_holds_an_index_or_a_quoted_key() {
11347        assert_eq!(steps("arr[0]"), vec![key("arr"), PathStep::Index(0)]);
11348        assert_eq!(steps("['!@#$%^']"), vec![key("!@#$%^")]);
11349        assert_eq!(
11350            steps("translations['hello world!']"),
11351            vec![key("translations"), key("hello world!")]
11352        );
11353    }
11354
11355    /// The corpus writes one path four ways, and Hasura reads them all alike.
11356    #[test]
11357    fn a_dot_before_a_bracket_changes_nothing() {
11358        let expected = vec![key("objs"), PathStep::Index(0), key("\u{4f60}\u{597d}")];
11359        for spelling in [
11360            "objs[0]['\u{4f60}\u{597d}']",
11361            "objs[0][\"\u{4f60}\u{597d}\"]",
11362            "objs[0].[\"\u{4f60}\u{597d}\"]",
11363            "objs.[0].[\"\u{4f60}\u{597d}\"]",
11364        ] {
11365            assert_eq!(steps(spelling), expected, "{}", spelling);
11366        }
11367    }
11368
11369    #[test]
11370    fn a_bracket_that_never_closes_is_refused() {
11371        assert!(parse_json_path("arr[0").is_err());
11372        assert!(parse_json_path("['key'").is_err());
11373    }
11374
11375    #[test]
11376    fn a_step_the_document_does_not_have_reads_null() {
11377        let document = Value::from_json(serde_json::json!({
11378            "obj": {"c1": "c2"},
11379            "arr": [1, 2, 3],
11380        }))
11381        .expect("a document");
11382        let at = |path| walk_json_path(&document, path).expect("the path parses");
11383        assert_eq!(at(".obj.c1"), Value::String("c2".to_string()));
11384        assert_eq!(at("arr[1]"), Value::Number(2.into()));
11385        assert_eq!(at("arr[9]"), Value::Null);
11386        assert_eq!(at(".obj.nothing"), Value::Null);
11387        // An index of an object, and a key of an array: neither reads
11388        // anything, which is what `#>` answers.
11389        assert_eq!(at("obj[0]"), Value::Null);
11390        assert_eq!(at("arr.first"), Value::Null);
11391    }
11392}