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, GeneratedSchema, MutationType, SchemaConfig};
11use crate::subscription::{
12    generate_subscription_fields, NotifyBroker, SubscriptionField as SubField,
13};
14use async_graphql::dynamic::*;
15use async_graphql::Value;
16use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
17use axum::extract::State;
18use axum::response::IntoResponse;
19use postrust_core::schema_cache::SchemaCache;
20use sqlx::PgPool;
21use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
22use std::sync::Arc;
23use tokio::sync::RwLock;
24use tracing::{debug, info, trace};
25
26/// GraphQL execution state shared across requests.
27pub struct GraphQLState {
28    /// Database connection pool
29    pub pool: PgPool,
30    /// Schema cache
31    pub schema_cache: Arc<SchemaCache>,
32    /// Generated GraphQL schema
33    pub generated_schema: GeneratedSchema,
34    /// async-graphql Schema (built dynamically)
35    ///
36    /// The unrestricted one: what an administrator is answered from, and what
37    /// every caller is answered from on a server with no permission document.
38    pub schema: Schema,
39    /// One schema per role the permission document names.
40    ///
41    /// Hasura builds a schema per role because a permission is a statement
42    /// about what exists, not about what is allowed: a role with no `select`
43    /// on a table has no field to name, and naming one is a validation failure
44    /// rather than a denial. Each is built from a schema cache already reduced
45    /// to what that role may see.
46    ///
47    /// Built at startup rather than on first use, so a permission document
48    /// that cannot be built fails while someone is watching rather than on the
49    /// first request from whichever role it broke.
50    ///
51    /// Keyed by the role and by whether the caller asked for what only a
52    /// backend may name -- which is a second schema rather than a check,
53    /// because `backend_only` decides whether a field exists.
54    pub role_schemas: HashMap<(String, bool), Schema>,
55    /// What a role the document never names is answered from.
56    ///
57    /// Not a refusal. To Hasura an unrecognised role is a role with nothing
58    /// granted, so it gets a schema with nothing in it -- and a request is
59    /// answered `field 'user' not found in type: 'query_root'`, or `no
60    /// mutations exist` where there is no mutation root at all, rather than
61    /// with a complaint about the role. `address_permission_error` sends
62    /// `X-Hasura-Role: merchant`, which no permission mentions, and expects
63    /// exactly that.
64    ///
65    /// One schema for every such role rather than one each: they are all the
66    /// same schema, since what they were granted is the same nothing.
67    pub ungranted_schema: Option<Schema>,
68    /// Schema configuration
69    pub config: SchemaConfig,
70    /// Subscription fields
71    pub subscription_fields: Vec<SubField>,
72    /// Notification broker for subscriptions
73    pub broker: Arc<RwLock<Option<NotifyBroker>>>,
74}
75
76/// Build one schema for each role the permission document names.
77///
78/// Each is built from a cache reduced to what that role may see, so nothing in
79/// the builders needs to know a permission exists -- a table this role cannot
80/// read is one the code generating root fields cannot see either. See
81/// [`crate::role`] for why the filtering is done there rather than threaded
82/// through.
83///
84/// Empty when the document carries no permissions, which is what leaves an
85/// unconfigured server building exactly the one schema it always did.
86fn build_role_schemas(
87    schema_cache: &Arc<SchemaCache>,
88    config: &SchemaConfig,
89) -> Result<HashMap<(String, bool), Schema>, GraphQLError> {
90    if !config.names.has_permissions() {
91        return Ok(HashMap::new());
92    }
93
94    let mut schemas = HashMap::new();
95    for role in config.names.roles() {
96        // An administrator is not a role with permissions; it is the caller
97        // the permissions do not apply to. Building it a restricted schema
98        // would take the bypass away.
99        if role == postrust_auth::hasura::ADMIN_ROLE {
100            continue;
101        }
102
103        // A second schema only where the role has something a backend alone
104        // may name. Otherwise the one schema answers both, since there is
105        // nothing for the flag to hide.
106        let variants: &[bool] = match crate::role::has_backend_only(&config.names, role) {
107            true => &[false, true],
108            false => &[false],
109        };
110
111        for &backend in variants {
112            let view = Arc::new(crate::role::cache_for_role(
113                schema_cache,
114                &config.names,
115                role,
116                backend,
117            ));
118            let mut role_config = config.clone();
119            role_config.role = Some(role.to_string());
120
121            let generated = build_schema(&view, &role_config);
122            // Subscriptions are left to the unrestricted schema for now: a live
123            // query is one this server answers from notifications rather than from
124            // the query root, so restricting it is a different piece of work from
125            // restricting a read.
126            let built = build_dynamic_schema(
127                &generated,
128                &view,
129                None,
130                role_config.max_rows,
131                Arc::new(role_config.names.clone()),
132                role_config.subscription_refresh(),
133                Some(role),
134            );
135
136            match built {
137                Ok(schema) => {
138                    schemas.insert((role.to_string(), backend), schema);
139                }
140                // One role's permissions being unbuildable is not a reason for
141                // every other role to lose its API, which is what returning here
142                // would mean -- the caller serves no GraphQL at all on an error.
143                // The role is left without a schema instead, which the request
144                // path already reads as a refusal: it fails closed, loudly, and
145                // alone.
146                Err(e) => tracing::error!(
147                    "GraphQL schema for role \"{}\" could not be built, \
148                 so that role is refused: {}",
149                    role,
150                    e
151                ),
152            }
153        }
154    }
155
156    tracing::info!("GraphQL schemas built for {} roles", schemas.len());
157    Ok(schemas)
158}
159
160/// The schema a role the document never names is answered from.
161///
162/// Built the same way every other role's is, from a cache reduced by a role
163/// nothing was granted to -- which leaves no tables, so it is the same schema
164/// whatever the unrecognised role is called. `None` where there is no
165/// permission document, since then no role is unrecognised.
166fn build_ungranted_schema(
167    schema_cache: &Arc<SchemaCache>,
168    config: &SchemaConfig,
169) -> Option<Schema> {
170    if !config.names.has_permissions() {
171        return None;
172    }
173    // A name no permission can carry: `roles()` reads them out of the
174    // document, and this one is not a role anybody wrote down.
175    const NOBODY: &str = "\u{0}ungranted";
176    let view = Arc::new(crate::role::cache_for_role(
177        schema_cache,
178        &config.names,
179        NOBODY,
180        false,
181    ));
182    let mut role_config = config.clone();
183    role_config.role = Some(NOBODY.to_string());
184    let generated = build_schema(&view, &role_config);
185    build_dynamic_schema(
186        &generated,
187        &view,
188        None,
189        role_config.max_rows,
190        Arc::new(role_config.names.clone()),
191        role_config.subscription_refresh(),
192        Some(NOBODY),
193    )
194    .map_err(|e| {
195        tracing::error!(
196            "the schema for a role with nothing granted could not be built,              so such a role is refused instead: {}",
197            e
198        )
199    })
200    .ok()
201}
202
203impl GraphQLState {
204    /// Create new GraphQL state from schema cache.
205    pub fn new(
206        pool: PgPool,
207        schema_cache: Arc<SchemaCache>,
208        config: SchemaConfig,
209    ) -> Result<Self, GraphQLError> {
210        let generated_schema = build_schema(&schema_cache, &config);
211        let subscription_fields = if config.enable_subscriptions {
212            generate_subscription_fields(&schema_cache, &generated_schema)
213        } else {
214            Vec::new()
215        };
216        let schema = build_dynamic_schema(
217            &generated_schema,
218            &schema_cache,
219            if config.enable_subscriptions {
220                Some(subscription_fields.as_slice())
221            } else {
222                None
223            },
224            config.max_rows,
225            Arc::new(config.names.clone()),
226            config.subscription_refresh(),
227            None,
228        )?;
229
230        let role_schemas = build_role_schemas(&schema_cache, &config)?;
231        let ungranted_schema = build_ungranted_schema(&schema_cache, &config);
232
233        Ok(Self {
234            pool: pool.clone(),
235            schema_cache,
236            generated_schema,
237            schema,
238            role_schemas,
239            ungranted_schema,
240            config,
241            subscription_fields,
242            broker: Arc::new(RwLock::new(None)),
243        })
244    }
245
246    /// Which schema answers for a caller speaking as this role.
247    ///
248    /// A role the document does not name is answered from the schema with
249    /// nothing in it, which is Hasura's reading: an unrecognised role is a
250    /// role that was granted nothing, not an error. Three things are
251    /// deliberately not that: a server with no permission document answers
252    /// everyone from the unrestricted schema, an administrator is answered
253    /// from it too -- permissions are rules for everyone else -- and so is a
254    /// request on a server where the layer is off.
255    ///
256    /// `None` only where the empty schema itself could not be built, which the
257    /// caller still turns into a refusal.
258    pub fn schema_for(&self, role: Option<&str>, backend: bool) -> Option<&Schema> {
259        if !self.config.names.has_permissions() {
260            return Some(&self.schema);
261        }
262        match role {
263            None | Some(postrust_auth::hasura::ADMIN_ROLE) => Some(&self.schema),
264            Some(role) => self
265                .role_schemas
266                .get(&(role.to_string(), backend))
267                // A role with nothing backend-only has one schema, under
268                // `false`, and it answers a backend caller too.
269                .or_else(|| self.role_schemas.get(&(role.to_string(), false)))
270                .or(self.ungranted_schema.as_ref()),
271        }
272    }
273
274    /// Rebuild the schema (e.g., after schema cache refresh).
275    #[allow(clippy::needless_update)]
276    pub fn rebuild(&mut self) -> Result<(), GraphQLError> {
277        self.role_schemas = build_role_schemas(&self.schema_cache, &self.config)?;
278        self.ungranted_schema = build_ungranted_schema(&self.schema_cache, &self.config);
279        self.generated_schema = build_schema(&self.schema_cache, &self.config);
280        self.subscription_fields = if self.config.enable_subscriptions {
281            generate_subscription_fields(&self.schema_cache, &self.generated_schema)
282        } else {
283            Vec::new()
284        };
285        self.schema = build_dynamic_schema(
286            &self.generated_schema,
287            &self.schema_cache,
288            if self.config.enable_subscriptions {
289                Some(self.subscription_fields.as_slice())
290            } else {
291                None
292            },
293            self.config.max_rows,
294            Arc::new(self.config.names.clone()),
295            self.config.subscription_refresh(),
296            None,
297        )?;
298        Ok(())
299    }
300
301    /// Initialize the subscription broker.
302    ///
303    /// This should be called after creating the state to enable subscriptions.
304    pub async fn init_subscriptions(&self) -> Result<(), crate::subscription::BrokerError> {
305        if !self.config.enable_subscriptions {
306            return Ok(());
307        }
308
309        let broker = NotifyBroker::new(self.pool.clone());
310
311        // Collect all channels to listen on
312        let channels: Vec<String> = self
313            .subscription_fields
314            .iter()
315            .map(|f| f.channel_name())
316            .collect();
317
318        if !channels.is_empty() {
319            broker.start(channels).await?;
320            info!(
321                "Subscription broker started with {} channels",
322                self.subscription_fields.len()
323            );
324        }
325
326        // Store the broker
327        let mut broker_guard = self.broker.write().await;
328        *broker_guard = Some(broker);
329
330        Ok(())
331    }
332
333    /// Stop the subscription broker.
334    pub async fn stop_subscriptions(&self) {
335        let broker_guard = self.broker.read().await;
336        if let Some(broker) = broker_guard.as_ref() {
337            broker.stop().await;
338        }
339    }
340
341    /// Get the notification broker.
342    pub async fn get_broker(&self) -> Option<Arc<RwLock<Option<NotifyBroker>>>> {
343        Some(Arc::clone(&self.broker))
344    }
345}
346
347/// Handle a GraphQL request.
348pub async fn graphql_handler(
349    State(state): State<Arc<GraphQLState>>,
350    ctx: GraphQLContext,
351    req: GraphQLRequest,
352) -> GraphQLResponse {
353    let request = req
354        .into_inner()
355        .data(ctx)
356        .data(state.pool.clone())
357        .data(Arc::clone(&state.broker));
358    state.schema.execute(request).await.into()
359}
360
361/// Handle GraphQL WebSocket subscription upgrade.
362///
363/// This should be called with a WebSocket upgrade request to enable
364/// GraphQL subscriptions over WebSocket.
365pub async fn graphql_ws_handler(
366    State(state): State<Arc<GraphQLState>>,
367    protocol: async_graphql_axum::GraphQLProtocol,
368    ws: axum::extract::WebSocketUpgrade,
369) -> impl IntoResponse {
370    let schema = state.schema.clone();
371    let pool = state.pool.clone();
372    let broker = Arc::clone(&state.broker);
373
374    ws.protocols(["graphql-transport-ws", "graphql-ws"])
375        .on_upgrade(move |socket| async move {
376            let mut data = async_graphql::Data::default();
377            data.insert(pool);
378            data.insert(broker);
379
380            async_graphql_axum::GraphQLWebSocket::new(socket, schema, protocol)
381                .with_data(data)
382                .serve()
383                .await
384        })
385}
386
387/// Handle GraphQL playground request.
388pub async fn graphql_playground() -> impl axum::response::IntoResponse {
389    axum::response::Html(async_graphql::http::playground_source(
390        async_graphql::http::GraphQLPlaygroundConfig::new("/graphql")
391            .subscription_endpoint("/graphql/ws"),
392    ))
393}
394
395/// The column a field name refers to.
396///
397/// The name itself, unless the table exposes that column under another one.
398/// Every path from a request to SQL goes through here: a renamed column is a
399/// name in the schema and in nothing else, so the translation has to happen at
400/// each boundary rather than once.
401fn column_for<'a>(
402    names: &'a crate::names::NameOverrides,
403    schema: &str,
404    table: &str,
405    field: &'a str,
406) -> &'a str {
407    names.column_source(schema, table, field).unwrap_or(field)
408}
409
410/// The same, given the table itself.
411fn table_column_for<'a>(
412    names: &'a crate::names::NameOverrides,
413    table: &postrust_core::schema_cache::Table,
414    field: &'a str,
415) -> &'a str {
416    column_for(names, &table.schema, &table.name, field)
417}
418
419/// The select list that renames a table's columns to the fields they are
420/// exposed as, or `None` where nothing is renamed.
421///
422/// Applied to a projection over the table rather than to the table itself, so
423/// the row a computed field or a computed relationship is passed stays the
424/// table's own composite -- `cannot cast type record to author` is what
425/// happens when it does not.
426fn rename_projection(
427    table: &postrust_core::schema_cache::Table,
428    alias: &str,
429    names: &crate::names::NameOverrides,
430) -> Option<String> {
431    if !names.renames_columns(&table.schema, &table.name) {
432        return None;
433    }
434    let parts: Vec<String> = table
435        .columns
436        .values()
437        .map(|column| {
438            let exposed = names
439                .column(&table.schema, &table.name, &column.name)
440                .unwrap_or(&column.name);
441            format!(
442                "{}.{} AS {}",
443                postrust_sql::escape_ident(alias),
444                postrust_sql::escape_ident(&column.name),
445                postrust_sql::escape_ident(exposed)
446            )
447        })
448        .collect();
449    match parts.is_empty() {
450        true => None,
451        false => Some(parts.join(", ")),
452    }
453}
454
455/// A table's column types, keyed by the names those columns are exposed under.
456///
457/// [`row_json`] names the shape columns it has to rewrite as GeoJSON, and it
458/// names them in whatever the projection called them. Over a renamed
459/// projection that is the field name, not the column's.
460fn exposed_column_types(
461    table: &postrust_core::schema_cache::Table,
462    names: &crate::names::NameOverrides,
463) -> HashMap<String, String> {
464    table
465        .columns
466        .values()
467        .map(|column| {
468            let exposed = names
469                .column(&table.schema, &table.name, &column.name)
470                .unwrap_or(&column.name);
471            (exposed.to_string(), column.nominal_type.clone())
472        })
473        .collect()
474}
475
476/// The type a value is written as, which keeps the list an array column is.
477///
478/// Not the leaf scalar: `c34_text_array` is a `text[]`, and a client sending
479/// `$textArray: [String]` into it was refused for offering a list where the
480/// input said `String`. Every write is optional, so nothing here is non-null.
481fn write_type_ref(graphql_type: &crate::types::GraphQLType) -> TypeRef {
482    match graphql_type {
483        crate::types::GraphQLType::List(inner) => TypeRef::named_list(leaf_scalar_name(inner)),
484        other => TypeRef::named(leaf_scalar_name(other)),
485    }
486}
487
488/// The scalar at the bottom of a type, past any list wrapping.
489fn leaf_scalar_name(graphql_type: &crate::types::GraphQLType) -> String {
490    match graphql_type {
491        crate::types::GraphQLType::List(inner) => leaf_scalar_name(inner),
492        other => other.to_string(),
493    }
494}
495
496/// Build the dynamic async-graphql schema from our generated schema.
497fn build_dynamic_schema(
498    generated: &GeneratedSchema,
499    _schema_cache: &SchemaCache,
500    subscription_fields: Option<&[SubField]>,
501    max_rows: Option<i64>,
502    names: Arc<crate::names::NameOverrides>,
503    subscription_refresh: std::time::Duration,
504    // Whose schema this is, where it is one role's. What it decides here is
505    // which columns a write may name, which is not the same question as which
506    // columns may be read -- Hasura grants the two separately, and a role that
507    // may set a column it cannot read is ordinary rather than exotic.
508    role: Option<&str>,
509) -> Result<Schema, GraphQLError> {
510    // Create object types for each table
511    let mut object_types: HashMap<String, Object> = HashMap::new();
512
513    // A table with no readable field gets no type at all. That is not an
514    // empty table -- it is one this role may write and not read, and a GraphQL
515    // object type with no fields is not a legal type. Everything that would
516    // have returned it is left out with it.
517    for (type_name, obj) in &generated.object_types {
518        if obj.fields.is_empty() {
519            continue;
520        }
521        let relationships = generated
522            .relationship_fields
523            .get(type_name)
524            .map(|r| r.as_slice())
525            .unwrap_or(&[]);
526        let table_obj = create_object_type(obj, relationships);
527        object_types.insert(type_name.clone(), table_obj);
528    }
529
530    // Aggregate types: every readable table gets them, because every table has
531    // a count even when it has nothing to sum -- and so does a table a role may
532    // count and not read, which is the whole of what such a role was granted.
533    // Read from the roots that were generated rather than worked out again,
534    // for the reason the write inputs are: two answers to one question drift.
535    let countable: HashSet<&str> = generated
536        .query_fields
537        .iter()
538        .filter(|field| field.aggregates && !field.is_by_pk)
539        .map(|field| field.type_name.as_str())
540        .collect();
541    for (type_name, obj) in &generated.object_types {
542        if obj.fields.is_empty() && !countable.contains(type_name.as_str()) {
543            continue;
544        }
545        for aggregate_type in create_aggregate_types(type_name, obj) {
546            object_types.insert(aggregate_type.type_name().to_string(), aggregate_type);
547        }
548    }
549
550    // One mutation response per table that has any mutation, and only those:
551    // an unreferenced type is still a type, and a read-only view would
552    // otherwise contribute a response nothing can return.
553    let mutable: HashSet<&str> = generated
554        .mutation_fields
555        .iter()
556        .map(|f| {
557            // The table's own name. A bulk write already answers with the
558            // response type, and trimming the suffix here is what keeps
559            // `<t>_mutation_response_mutation_response` from being built: the
560            // set used to be carried by `insert_one` and `update_by_pk`
561            // naming the bare type, so a table with only bulk writes -- which
562            // is what a role that cannot read one has -- registered the wrong
563            // name and left the right one missing.
564            f.return_type
565                .trim_matches(|c| c == '[' || c == ']' || c == '!')
566                .trim_end_matches("_mutation_response")
567        })
568        .collect();
569    for base_name in mutable {
570        // Whether it has rows to give back. A write to a table this role
571        // cannot read answers with `affected_rows` and nothing else -- there
572        // is no row type for `returning` to be a list of.
573        let returning = generated
574            .object_types
575            .get(base_name)
576            .is_some_and(|object| !object.fields.is_empty());
577        object_types.insert(
578            mutation_response_type_name(base_name),
579            create_mutation_response_type(base_name, returning),
580        );
581    }
582
583    // Create query type. Resolvers need the relationship map to embed related
584    // rows, so it is shared into each closure.
585    let relationships = Arc::new(generated.relationship_fields.clone());
586    // (schema, table) -> GraphQL type name. A nested insert is handed a table
587    // by a relationship and has to find that table's relationships in turn,
588    // and the map they live in is keyed by type name -- which is not always
589    // the table's name, since a second schema prefixes it and a name may have
590    // been given.
591    let type_names: Arc<HashMap<(String, String), String>> = Arc::new(
592        generated
593            .object_types
594            .iter()
595            .map(|(type_name, object)| {
596                (
597                    (object.table.schema.clone(), object.table.name.clone()),
598                    type_name.clone(),
599                )
600            })
601            .collect(),
602    );
603    let query = add_function_fields(
604        create_query_type(
605            generated,
606            max_rows,
607            Arc::clone(&relationships),
608            Arc::clone(&names),
609        ),
610        generated,
611        false,
612        max_rows,
613        Arc::clone(&relationships),
614        Arc::clone(&names),
615    );
616
617    // Create mutation type
618    let mutation = if !generated.mutation_fields.is_empty()
619        || generated.function_fields.iter().any(|f| f.volatile)
620    {
621        Some(add_function_fields(
622            create_mutation_type(
623                generated,
624                Arc::clone(&relationships),
625                Arc::clone(&type_names),
626                Arc::clone(&names),
627                max_rows,
628                role,
629            ),
630            generated,
631            true,
632            max_rows,
633            Arc::clone(&relationships),
634            Arc::clone(&names),
635        ))
636    } else {
637        None
638    };
639
640    // Create subscription type if enabled
641    let subscription = subscription_fields.map(|_| {
642        create_subscription_type(
643            generated,
644            max_rows,
645            Arc::clone(&relationships),
646            Arc::clone(&names),
647            subscription_refresh,
648        )
649    });
650
651    // Build schema
652    // `query_root`, not `Query`. The root type's name is not private to the
653    // server: a fragment is declared `on query_root`, and a client that writes
654    // one names the type it was generated against.
655    let mut builder = Schema::build(
656        "query_root",
657        mutation.as_ref().map(|_| "mutation_root"),
658        subscription.as_ref().map(|_| "subscription_root"),
659    );
660
661    // Register all object types
662    for (_, obj) in object_types {
663        builder = builder.register(obj);
664    }
665
666    // Register query type
667    builder = builder.register(query);
668
669    // Register mutation type if present
670    if let Some(mutation) = mutation {
671        builder = builder.register(mutation);
672    }
673
674    // Register subscription type if present
675    if let Some(subscription) = subscription {
676        builder = builder.register(subscription);
677    }
678
679    // Register scalar types
680    // Every scalar the generated types actually name, rather than a fixed
681    // list. A `geometry` column, a `raster` column and a database enum each
682    // become a scalar under their own PostgreSQL name, because that is the
683    // name a client's query declares its variables with -- and a scalar the
684    // schema mentions but never registers makes the whole schema unbuildable.
685    let (bool_exp_inputs, bool_exp_scalars) = crate::input::bool_exp::build_inputs(
686        &generated.object_types,
687        &generated.relationship_fields,
688    );
689    for input in bool_exp_inputs {
690        builder = builder.register(input);
691    }
692
693    let mut scalar_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
694    for object in generated.object_types.values() {
695        // Both halves. A column a role may set without seeing is named by the
696        // insert input and by nothing a read produces, so collecting only the
697        // readable fields leaves its scalar unregistered -- and a scalar the
698        // schema mentions but never registers makes the whole schema
699        // unbuildable, which costs the role every field it has.
700        for field in object.fields.iter().chain(&object.writable_fields) {
701            scalar_names.insert(leaf_scalar_name(&field.graphql_type));
702        }
703    }
704    // Used as an argument type in its own right, whether or not any column is
705    // one: `objects`, `_set` and the mutation inputs are still JSON.
706    scalar_names.insert("JSON".to_string());
707    // A function's arguments name scalars of their own: a table may have no
708    // geometry column while a function takes one.
709    for function in &generated.function_fields {
710        for (_, pg_type, _) in &function.arguments {
711            scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
712        }
713    }
714    // The same for the functions behind computed fields, whose arguments are
715    // written where the field is asked for rather than at a root.
716    for object in generated.object_types.values() {
717        for field in &object.fields {
718            for (_, pg_type, _) in &field.arguments {
719                scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
720            }
721        }
722    }
723    for relationships in generated.relationship_fields.values() {
724        for relationship in relationships {
725            for (_, pg_type, _) in &relationship.arguments {
726                scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
727            }
728        }
729    }
730
731    // Every scalar the boolean expressions name, which is more than the
732    // scalars the columns are: a cast from a geometry names `geography`, and a
733    // raster comparison names `geometry`. Taken from what was generated rather
734    // than listed here, because a list here is a second place to keep in step
735    // and it fell out of step twice.
736    scalar_names.extend(bool_exp_scalars);
737
738    for name in scalar_names {
739        if matches!(name.as_str(), "Int" | "Float" | "String" | "Boolean" | "ID") {
740            continue;
741        }
742        // An enum table's type is an enum, not a scalar. It reaches this list
743        // because a column typed as one names it, and registering both would
744        // define the same type twice.
745        if generated.enum_types.contains_key(&name) {
746            continue;
747        }
748        builder = builder
749            .register(Scalar::new(&name).description(format!("The PostgreSQL `{}` type.", name)));
750    }
751
752    // Register the boolean expression inputs -- one per table, plus one
753    // comparison input per scalar the tables use.
754
755    // A key as one object, for `update_x_by_pk(pk_columns: {...})`. Only the
756    // by-key update spells its key this way; the by-key query and delete take
757    // the columns as arguments, and both spellings are what a generated client
758    // already sends.
759    let mut key_inputs: HashMap<String, InputObject> = HashMap::new();
760    for field in &generated.mutation_fields {
761        if field.mutation_type != MutationType::UpdateByPk || field.pk_columns.is_empty() {
762            continue;
763        }
764        let base_name = field
765            .return_type
766            .trim_matches(|c| c == '[' || c == ']' || c == '!')
767            .trim_end_matches("_mutation_response");
768        let type_name = format!("{}_pk_columns_input", base_name);
769        let mut input = InputObject::new(&type_name)
770            .description(format!("The primary key of one {} row.", base_name));
771        for (column, pg_type) in &field.pk_columns {
772            input = input.field(InputValue::new(
773                names
774                    .column(&field.schema_name, &field.table_name, column)
775                    .unwrap_or(column),
776                TypeRef::named_nn(pk_argument_type(pg_type)),
777            ));
778        }
779        key_inputs.insert(type_name, input);
780    }
781    for (_, input) in key_inputs {
782        builder = builder.register(input);
783    }
784
785    // What a mutation writes. `objects` and `_set` were `JSON`, which accepts
786    // anything and lets a client generate nothing: no completion, no codegen,
787    // and a misspelled column reaching the database instead of being caught
788    // before the request was sent. The same argument that made `where` a real
789    // type applies unchanged.
790    // Which types have an `_insert_input` at all, which is what a nested write
791    // names. Collected first because a relationship may point at a table this
792    // loop has not reached yet -- and because the answer is now a permission
793    // question: a role granted `insert` on `article` and only `select` on
794    // `author` gets an `article_insert_input`, and writing `author` inside it
795    // would name a type nobody registered. async-graphql refuses to build a
796    // schema with a dangling reference, so the whole role would have no schema
797    // over one field it was never allowed to use.
798    // Which types actually got each write input, recorded as they are built
799    // rather than predicted from the object type's fields. The two answers
800    // used to be the same and are not any more: an input is built from the
801    // write permission's columns, so a table whose readable columns include a
802    // number may still have no `_inc_input` -- and an argument naming a type
803    // nobody registered is a schema async-graphql refuses to build, which
804    // costs the role every field it has rather than the one it cannot use.
805    let insertable_types: HashSet<&str> = generated
806        .object_types
807        .iter()
808        .filter(|(_, object)| crate::input::mutation::is_insertable(&object.table))
809        .map(|(type_name, _)| type_name.as_str())
810        .collect();
811
812    for (type_name, object) in &generated.object_types {
813        let table = &object.table;
814        // Which columns a write may name, which is the write permission's
815        // answer and not the read permission's. A role granted `columns:
816        // ["age"]` on insert may not name `is_user` even where it can read it,
817        // and the corpus tests exactly that: `field 'is_user' not found in
818        // type: 'resident_insert_input'`.
819        // Only real columns are written. A computed field is a function of the
820        // row and a relationship is handled separately below.
821        let writable_named = |allowed: Option<&crate::names::ColumnSet>| {
822            object
823                .writable_fields
824                .iter()
825                .filter(|field| {
826                    let column = table_column_for(&names, table, &field.name);
827                    table.get_column(column).is_some()
828                        && allowed.is_none_or(|set| set.allows(column))
829                })
830                .collect::<Vec<&crate::schema::object::GraphQLField>>()
831        };
832        let writable = writable_named(None);
833        let insert_fields = write_fields(object, &names, role, crate::role::Verb::Insert);
834        let update_fields = write_fields(object, &names, role, crate::role::Verb::Update);
835        if writable.is_empty() {
836            continue;
837        }
838
839        let relationships = generated
840            .relationship_fields
841            .get(type_name)
842            .map(|r| r.as_slice())
843            .unwrap_or(&[]);
844        let conflict_type = match table.unique_constraints.is_empty() {
845            true => None,
846            false => Some(format!("{}_on_conflict", type_name)),
847        };
848
849        if crate::input::mutation::is_insertable(table) {
850            let mut insert = InputObject::new(format!("{}_insert_input", type_name))
851                .description(format!("The columns of a new {} row.", type_name));
852            let mut taken: HashSet<&str> = HashSet::new();
853            for field in &insert_fields {
854                // Every column optional: which ones the database insists on is
855                // the database's answer, and a column that is NOT NULL with a
856                // default does not have to be given.
857                taken.insert(field.name.as_str());
858                insert = insert.field(InputValue::new(
859                    &field.name,
860                    write_type_ref(&field.graphql_type),
861                ));
862            }
863            // A nested write: the rows to insert beside this one. Only where
864            // the far side is something this schema can write -- see
865            // `insertable_types`.
866            for relationship in relationships {
867                if !insertable_types.contains(relationship.target_type.as_str()) {
868                    continue;
869                }
870                if !taken.insert(relationship.name.as_str()) {
871                    continue;
872                }
873                let suffix = match relationship.is_list {
874                    true => "arr_rel_insert_input",
875                    false => "obj_rel_insert_input",
876                };
877                insert = insert.field(InputValue::new(
878                    &relationship.name,
879                    TypeRef::named(format!("{}_{}", relationship.target_type, suffix)),
880                ));
881            }
882            builder = builder.register(insert);
883
884            // How this table is written as somebody else's nested row. Both
885            // shapes carry an `on_conflict`, which is what makes a nested
886            // upsert expressible.
887            let data_type = format!("{}_insert_input", type_name);
888            let mut object_rel = InputObject::new(format!("{}_obj_rel_insert_input", type_name))
889                .description(format!("One {} row written beside its parent.", type_name))
890                .field(InputValue::new("data", TypeRef::named_nn(&data_type)));
891            let mut array_rel = InputObject::new(format!("{}_arr_rel_insert_input", type_name))
892                .description(format!("{} rows written beside their parent.", type_name))
893                .field(InputValue::new(
894                    "data",
895                    TypeRef::named_nn_list_nn(&data_type),
896                ));
897            if let Some(conflict) = &conflict_type {
898                object_rel =
899                    object_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
900                array_rel =
901                    array_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
902            }
903            builder = builder.register(object_rel).register(array_rel);
904        }
905
906        if crate::input::mutation::is_updatable(table) {
907            let mut set = InputObject::new(format!("{}_set_input", type_name))
908                .description(format!("Columns of {} to replace.", type_name));
909            let mut numeric = InputObject::new(format!("{}_inc_input", type_name))
910                .description(format!("Columns of {} to add to.", type_name));
911            let mut any_numeric = false;
912            let mut any_jsonb = false;
913            for field in &update_fields {
914                if matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
915                    any_jsonb = true;
916                }
917                set = set.field(InputValue::new(
918                    &field.name,
919                    write_type_ref(&field.graphql_type),
920                ));
921                // Only a number can be added to, and a list of them is not a
922                // number: `_inc` takes the scalar or nothing.
923                if crate::schema::aggregate::is_incrementable(&field.graphql_type) {
924                    any_numeric = true;
925                    numeric = numeric.field(InputValue::new(
926                        &field.name,
927                        TypeRef::named(leaf_scalar_name(&field.graphql_type)),
928                    ));
929                }
930            }
931            builder = builder.register(set);
932            // A table with nothing to add to gets no type for adding to it.
933            if any_numeric {
934                builder = builder.register(numeric);
935            }
936
937            // One entry of `update_x_many`: a filter and the values to write
938            // where it matches. The whole list runs in one transaction, in the
939            // order it was given, which is what makes it different from
940            // sending the updates one at a time.
941            let mut updates = InputObject::new(format!("{}_updates", type_name))
942                .description(format!(
943                    "One update to {}: which rows, and what to write.",
944                    type_name
945                ))
946                .field(InputValue::new(
947                    "where",
948                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(type_name)),
949                ))
950                .field(InputValue::new(
951                    "_set",
952                    TypeRef::named(format!("{}_set_input", type_name)),
953                ));
954            if any_numeric {
955                updates = updates.field(InputValue::new(
956                    "_inc",
957                    TypeRef::named(format!("{}_inc_input", type_name)),
958                ));
959            }
960            // What a document column may be told to do, one input per
961            // operator: each takes a value of a different shape -- a
962            // document, a key, an index, a path -- and only the columns that
963            // hold documents may be told any of it. A table with none is not
964            // given the operators at all, which is what says in the schema
965            // that `_append` is not a thing to write there.
966            for (operator, _) in JSONB_OPERATORS {
967                if !any_jsonb {
968                    break;
969                }
970                updates = updates.field(InputValue::new(
971                    *operator,
972                    TypeRef::named(jsonb_operator_input(type_name, operator)),
973                ));
974            }
975            builder = builder.register(updates);
976            if any_jsonb {
977                for (operator, item) in JSONB_OPERATORS {
978                    let mut input =
979                        InputObject::new(jsonb_operator_input(type_name, operator)).description(
980                            format!("Columns of {} to apply `{}` to.", type_name, operator),
981                        );
982                    for field in &update_fields {
983                        if !matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
984                            continue;
985                        }
986                        input = input.field(InputValue::new(&field.name, item()));
987                    }
988                    builder = builder.register(input);
989                }
990            }
991        }
992    }
993
994    // A function's own arguments, under a name of their own so they cannot
995    // collide with `where` or `limit`.
996    for function in &generated.function_fields {
997        if function.arguments.is_empty() {
998            continue;
999        }
1000        let mut args = InputObject::new(format!("{}_args", function.name))
1001            .description(format!("Arguments to {}.", function.name));
1002        for (name, pg_type, _) in &function.arguments {
1003            let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1004            // Nullable, whether or not the parameter has a default. Every
1005            // PostgreSQL argument accepts a null, and a client passing one
1006            // through a variable declares it as the nullable type -- `query
1007            // ($point: json)` used where `json!` was expected is a query the
1008            // spec's variable rule refuses. Whether a defaulted argument may
1009            // be left out entirely is enforced where the call is written,
1010            // which is the only place that can tell.
1011            args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1012        }
1013        builder = builder.register(args);
1014    }
1015
1016    // The same for a computed relationship's function, which takes its
1017    // arguments where it is embedded rather than at the root. Named after the
1018    // type and the field, since two tables may reach the same function and one
1019    // table may reach two.
1020    for (type_name, relationships) in &generated.relationship_fields {
1021        for relationship in relationships {
1022            if relationship.arguments.is_empty() {
1023                continue;
1024            }
1025            let mut args = InputObject::new(computed_args_type_name(type_name, &relationship.name))
1026                .description(format!(
1027                    "Arguments to {}, beside the row it is asked of.",
1028                    relationship.name
1029                ));
1030            for (name, pg_type, _) in &relationship.arguments {
1031                let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1032                // Nullable, for the reason the function arguments above are.
1033                args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1034            }
1035            builder = builder.register(args);
1036        }
1037    }
1038
1039    // And for a computed *column*'s function, which likewise takes its
1040    // arguments where the field is asked for. `locations { distance(args: {
1041    // from: ... }) }` -- the field is a function of the row and of what the
1042    // caller wants measured against it.
1043    for (type_name, object) in &generated.object_types {
1044        for field in &object.fields {
1045            if field.arguments.is_empty() {
1046                continue;
1047            }
1048            let mut args = InputObject::new(computed_args_type_name(type_name, &field.name))
1049                .description(format!(
1050                    "Arguments to {}, beside the row it is asked of.",
1051                    field.name
1052                ));
1053            for (name, pg_type, _) in &field.arguments {
1054                let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1055                // Nullable, for the reason the function arguments above are.
1056                args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1057            }
1058            builder = builder.register(args);
1059        }
1060    }
1061
1062    // What a predicate over a related set may ask. Registered only for the
1063    // tables something points at with a to-many relationship, which is the
1064    // only place the field naming them exists.
1065    {
1066        use crate::input::bool_exp as be;
1067        let mut targets: BTreeSet<&str> = BTreeSet::new();
1068        for relationships in generated.relationship_fields.values() {
1069            for relationship in relationships {
1070                if relationship.is_list {
1071                    targets.insert(relationship.target_type.as_str());
1072                }
1073            }
1074        }
1075        for target in targets {
1076            let Some(object) = generated.object_types.get(target) else {
1077                continue;
1078            };
1079            // A boolean column is what `bool_and` and `bool_or` fold, and a
1080            // GraphQL enum may not be empty -- so a table with none gets
1081            // neither the aggregates nor the enum naming their columns.
1082            let booleans: Vec<&str> = object
1083                .fields
1084                .iter()
1085                .filter(|field| {
1086                    object.table.get_column(&field.name).is_some()
1087                        && matches!(field.graphql_type, crate::types::GraphQLType::Boolean)
1088                })
1089                .map(|field| field.name.as_str())
1090                .collect();
1091
1092            let mut over = InputObject::new(be::aggregate_bool_exp_type_name(target)).description(
1093                format!("A question about the whole set of related {} rows.", target),
1094            );
1095            for function in be::AGGREGATE_PREDICATES {
1096                if function != "count" && booleans.is_empty() {
1097                    continue;
1098                }
1099                over = over.field(InputValue::new(
1100                    function,
1101                    TypeRef::named(be::aggregate_bool_exp_function(target, function)),
1102                ));
1103
1104                // `count` may be told which columns to count, as the field
1105                // itself may; `bool_and` folds exactly one.
1106                let (arguments, predicate) = match function {
1107                    "count" => (
1108                        TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1109                            target,
1110                        )),
1111                        be::comparison_type_name("Int"),
1112                    ),
1113                    _ => (
1114                        TypeRef::named_nn(be::aggregate_bool_exp_columns(target, function)),
1115                        be::comparison_type_name("Boolean"),
1116                    ),
1117                };
1118                let mut input = InputObject::new(be::aggregate_bool_exp_function(target, function))
1119                    .description(format!(
1120                        "`{}` over the related {} rows, and what it has to be.",
1121                        function, target
1122                    ))
1123                    .field(InputValue::new("arguments", arguments))
1124                    .field(InputValue::new("distinct", TypeRef::named("Boolean")))
1125                    .field(InputValue::new(
1126                        "filter",
1127                        TypeRef::named(be::bool_exp_type_name(target)),
1128                    ))
1129                    .field(InputValue::new("predicate", TypeRef::named_nn(predicate)));
1130                if function == "count" {
1131                    input = input.description(format!(
1132                        "How many related {} rows there have to be.",
1133                        target
1134                    ));
1135                }
1136                builder = builder.register(input);
1137
1138                if function != "count" {
1139                    let mut columns = Enum::new(be::aggregate_bool_exp_columns(target, function))
1140                        .description(format!("A boolean column of {}.", target));
1141                    for name in &booleans {
1142                        columns = columns.item(EnumItem::new(*name));
1143                    }
1144                    builder = builder.register(columns);
1145                }
1146            }
1147            builder = builder.register(over);
1148        }
1149    }
1150
1151    // Upserts. A table with no unique constraint has no conflict to resolve,
1152    // and a GraphQL enum may not be empty, so it gets none of these types
1153    // rather than an unusable set of them.
1154    for (type_name, object) in &generated.object_types {
1155        // What an upsert may write, which is the update permission's answer
1156        // and not the read permission's: `update_columns` names columns to
1157        // overwrite, so a role that may set a column without seeing it may
1158        // name it here. Where no update permission narrows them these are the
1159        // table's own columns, which is what the enum listed before.
1160        let updatable_columns = write_fields(object, &names, role, crate::role::Verb::Update);
1161        if object.table.unique_constraints.is_empty() {
1162            continue;
1163        }
1164
1165        let mut constraints = Enum::new(format!("{}_constraint", type_name)).description(format!(
1166            "A uniqueness of {} that an insert may conflict with.",
1167            type_name
1168        ));
1169        for (name, columns) in &object.table.unique_constraints {
1170            constraints = constraints
1171                .item(EnumItem::new(name).description(format!("unique ({})", columns.join(", "))));
1172        }
1173
1174        let mut updatable = Enum::new(format!("{}_update_column", type_name))
1175            .description(format!("A column of {} an upsert may write.", type_name));
1176        for field in &updatable_columns {
1177            updatable = updatable.item(EnumItem::new(&field.name));
1178        }
1179        // An update permission granting no column leaves the enum with no
1180        // members, which is not a legal type -- and dropping the enum would
1181        // drop `on_conflict` with it, so an insert that names one would fail
1182        // to build rather than be refused. Hasura's answer is a member that
1183        // names no column: the upsert is still expressible, `update_columns:
1184        // []` still means `DO NOTHING`, and naming the placeholder is refused
1185        // where it is used. `article_on_conflict_restricted_role.yaml` tests
1186        // both halves.
1187        if updatable_columns.is_empty() {
1188            updatable = updatable.item(
1189                EnumItem::new(PLACEHOLDER_COLUMN)
1190                    .description("No column may be written by this upsert."),
1191            );
1192        }
1193
1194        let on_conflict = InputObject::new(format!("{}_on_conflict", type_name))
1195            .description(format!(
1196                "What to do when an insert into {} conflicts.",
1197                type_name
1198            ))
1199            .field(InputValue::new(
1200                "constraint",
1201                TypeRef::named_nn(format!("{}_constraint", type_name)),
1202            ))
1203            // An empty list is `DO NOTHING`, which is how Hasura spells "leave
1204            // the row that is already there alone" -- and is the default, so
1205            // `on_conflict: {constraint: article_pkey}` means exactly that
1206            // rather than being refused for saying nothing about columns.
1207            .field(
1208                InputValue::new(
1209                    "update_columns",
1210                    TypeRef::named_nn_list_nn(format!("{}_update_column", type_name)),
1211                )
1212                .default_value(Value::List(Vec::new())),
1213            )
1214            .field(InputValue::new(
1215                "where",
1216                TypeRef::named(crate::input::bool_exp::bool_exp_type_name(type_name)),
1217            ));
1218
1219        builder = builder
1220            .register(constraints)
1221            .register(updatable)
1222            .register(on_conflict);
1223    }
1224
1225    // The enums generated from enum tables.
1226    for (type_name, members) in &generated.enum_types {
1227        let mut generated_enum =
1228            Enum::new(type_name).description(format!("The values {} allows.", type_name));
1229        for (value, comment) in members {
1230            let item = EnumItem::new(value);
1231            generated_enum = generated_enum.item(match comment {
1232                Some(description) => item.description(description),
1233                None => item,
1234            });
1235        }
1236        builder = builder.register(generated_enum);
1237    }
1238
1239    // Ordering: one input per table, one column enum per table, and the single
1240    // direction enum they all share.
1241    let (order_inputs, order_enums) = crate::input::order_by::build_inputs(
1242        &generated.object_types,
1243        &generated.relationship_fields,
1244    );
1245    for input in order_inputs {
1246        builder = builder.register(input);
1247    }
1248    for enum_type in order_enums {
1249        builder = builder.register(enum_type);
1250    }
1251
1252    builder
1253        .finish()
1254        .map_err(|e| GraphQLError::SchemaError(e.to_string()))
1255}
1256
1257/// Create an object type from a TableObjectType.
1258/// The enum member that stands in for an update permission granting no column.
1259///
1260/// Hasura's spelling, and it is load-bearing: `article_update_column` must be
1261/// a legal enum for `article_on_conflict` to exist, and `on_conflict` must
1262/// exist for an insert that carries one to be refused rather than unbuildable.
1263const PLACEHOLDER_COLUMN: &str = "_PLACEHOLDER";
1264
1265/// The name of a table's mutation response type.
1266pub fn mutation_response_type_name(base_name: &str) -> String {
1267    format!("{}_mutation_response", base_name)
1268}
1269
1270/// Build the `<table>_mutation_response` object.
1271///
1272/// Every bulk mutation answers with this rather than with the rows directly.
1273/// `affected_rows` is the count PostgreSQL reports, which is not the length of
1274/// `returning`: a client may ask for no rows back at all and still need to know
1275/// how many were touched, and that is the usual case for a delete.
1276fn create_mutation_response_type(base_name: &str, returning: bool) -> Object {
1277    let response_name = mutation_response_type_name(base_name);
1278    let row_type = base_name.to_string();
1279
1280    let response = Object::new(&response_name)
1281        .description(format!("The rows {} changed, and how many.", base_name))
1282        .field(Field::new(
1283            "affected_rows",
1284            TypeRef::named_nn(TypeRef::INT),
1285            |ctx| {
1286                FieldFuture::new(async move {
1287                    if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1288                        if let Some(value) = map.get(&async_graphql::Name::new("affected_rows")) {
1289                            return Ok(Some(FieldValue::value(value.clone())));
1290                        }
1291                    }
1292                    Ok(Some(FieldValue::value(Value::from(0))))
1293                })
1294            },
1295        ));
1296    // A table this role may write and not read has no row type, so there is
1297    // nothing for `returning` to be a list of. Hasura answers such a write
1298    // with the count alone, and the corpus asks for nothing else.
1299    if !returning {
1300        return response;
1301    }
1302    response.field(Field::new(
1303        "returning",
1304        TypeRef::named_nn_list_nn(row_type),
1305        |ctx| {
1306            FieldFuture::new(async move {
1307                let rows = match ctx.parent_value.as_value() {
1308                    Some(Value::Object(map)) => {
1309                        match map.get(&async_graphql::Name::new("returning")) {
1310                            Some(Value::List(items)) => items.clone(),
1311                            _ => Vec::new(),
1312                        }
1313                    }
1314                    _ => Vec::new(),
1315                };
1316                Ok(Some(FieldValue::list(
1317                    rows.into_iter().map(FieldValue::value),
1318                )))
1319            })
1320        },
1321    ))
1322}
1323
1324/// Build every aggregate type for one table.
1325///
1326/// Four kinds, and they nest: `<t>_aggregate` holds `aggregate` and `nodes`,
1327/// `<t>_aggregate_fields` holds `count` and one field per function, and each
1328/// function has a type of its own holding the columns it applies to.
1329fn create_aggregate_types(base_name: &str, object: &TableObjectType) -> Vec<Object> {
1330    use crate::schema::aggregate as agg;
1331
1332    let mut types = Vec::new();
1333
1334    // `<t>_aggregate`: the rows, and the numbers about them.
1335    let fields_type = agg::aggregate_fields_type_name(base_name);
1336    // Whether there are rows to hand back beside the numbers. A role granted
1337    // "how many" and not "which" has no row type, so `nodes` has nothing to be
1338    // a list of.
1339    let has_rows = !object.fields.is_empty();
1340    let mut over = Object::new(agg::aggregate_type_name(base_name))
1341        .description(match has_rows {
1342            true => format!("Aggregates over {}, with the rows themselves.", base_name),
1343            false => format!("Aggregates over {}.", base_name),
1344        })
1345        .field(Field::new(
1346            "aggregate",
1347            TypeRef::named(&fields_type),
1348            |ctx| FieldFuture::new(async move { Ok(child_of(&ctx, "aggregate")) }),
1349        ));
1350    if has_rows {
1351        over = over.field(Field::new(
1352            "nodes",
1353            TypeRef::named_nn_list_nn(base_name.to_string()),
1354            |ctx| {
1355                FieldFuture::new(async move {
1356                    let rows = match child_value(&ctx, "nodes") {
1357                        Some(Value::List(items)) => items,
1358                        _ => Vec::new(),
1359                    };
1360                    Ok(Some(FieldValue::list(
1361                        rows.into_iter().map(FieldValue::value),
1362                    )))
1363                })
1364            },
1365        ));
1366    }
1367    types.push(over);
1368
1369    // `<t>_aggregate_fields`: count, and one field per function.
1370    let mut count = Field::new("count", TypeRef::named_nn(TypeRef::INT), |ctx| {
1371        // Read under the name it was asked for, not under `count`.
1372        // Two counts of different things sit in one selection --
1373        // `count` beside `distinct_authors: count(columns: [author_id],
1374        // distinct: true)` -- and they are different numbers.
1375        let key = ctx.ctx.field().alias().unwrap_or("count").to_string();
1376        FieldFuture::new(async move {
1377            Ok(Some(FieldValue::value(
1378                child_value(&ctx, &key).unwrap_or(Value::from(0)),
1379            )))
1380        })
1381    });
1382    // `count(columns:)` counts the rows where those columns are not null, and
1383    // `distinct` counts distinct values among them. Neither is offered where
1384    // the role may name no column: `count` is then a count of rows and nothing
1385    // else, and the corpus tests exactly that -- `'count' has no argument
1386    // named 'columns'`.
1387    if has_rows {
1388        count = count
1389            .argument(InputValue::new(
1390                "columns",
1391                TypeRef::named_nn_list(agg_select_column(base_name)),
1392            ))
1393            .argument(InputValue::new("distinct", TypeRef::named("Boolean")));
1394    }
1395    let mut aggregate_fields = Object::new(&fields_type)
1396        .description(format!("Aggregate functions over {}.", base_name))
1397        .field(count);
1398
1399    for (function, returns, columns) in agg::functions_for(object) {
1400        let function_type = agg::function_fields_type_name(base_name, function);
1401        let mut per_column = Object::new(&function_type).description(format!(
1402            "`{}` of each {} column it applies to.",
1403            function, base_name
1404        ));
1405        for column in &columns {
1406            let column_name = column.clone();
1407            let type_name = agg::field_type_for(object, column, returns);
1408            per_column =
1409                per_column.field(Field::new(column, TypeRef::named(type_name), move |ctx| {
1410                    let column_name = column_name.clone();
1411                    FieldFuture::new(async move {
1412                        Ok(child_value(&ctx, &column_name).map(FieldValue::value))
1413                    })
1414                }));
1415        }
1416        types.push(per_column);
1417
1418        let owned = function.to_string();
1419        aggregate_fields = aggregate_fields.field(Field::new(
1420            function,
1421            TypeRef::named(&function_type),
1422            move |ctx| {
1423                let owned = owned.clone();
1424                FieldFuture::new(async move { Ok(child_of(&ctx, &owned)) })
1425            },
1426        ));
1427    }
1428
1429    types.push(aggregate_fields);
1430    types
1431}
1432
1433/// The column enum a `count(columns:)` draws from.
1434fn agg_select_column(base_name: &str) -> String {
1435    crate::input::order_by::select_column_type_name(base_name)
1436}
1437
1438/// One key of the parent object, as a value.
1439fn child_value(ctx: &ResolverContext<'_>, key: &str) -> Option<Value> {
1440    match ctx.parent_value.as_value() {
1441        Some(Value::Object(map)) => map.get(&async_graphql::Name::new(key)).cloned(),
1442        _ => None,
1443    }
1444}
1445
1446/// One key of the parent object, as a resolvable child.
1447///
1448/// `None` where the key is absent or null, which is what a client that did not
1449/// ask for that aggregate should see.
1450fn child_of<'a>(ctx: &ResolverContext<'_>, key: &str) -> Option<FieldValue<'a>> {
1451    match child_value(ctx, key) {
1452        None | Some(Value::Null) => None,
1453        Some(value) => Some(FieldValue::value(value)),
1454    }
1455}
1456
1457/// An error carrying the code Hasura would give it.
1458///
1459/// The default coding reads the message, and a refusal written here has no
1460/// word in it that a message from PostgreSQL would not also have -- so it says
1461/// so itself rather than being guessed at.
1462fn coded_error(code: &'static str, message: impl Into<String>) -> async_graphql::Error {
1463    let mut error = async_graphql::Error::new(message);
1464    let mut extensions = async_graphql::ErrorExtensionValues::default();
1465    extensions.set("code", code);
1466    error.extensions = Some(extensions);
1467    error
1468}
1469
1470/// A refusal the client could have avoided by writing the request differently.
1471fn validation_error(message: impl Into<String>) -> async_graphql::Error {
1472    coded_error("validation-failed", message)
1473}
1474
1475/// A database error, classified the way Hasura classifies it.
1476///
1477/// PostgreSQL's own message is the whole of what either server says about
1478/// what went wrong; what Hasura adds is a word for *which kind* of wrong,
1479/// taken from the SQLSTATE rather than from the text: `Uniqueness violation.
1480/// duplicate key value violates unique constraint "author_name_key"`. A client
1481/// reporting that to a user is reporting text it already ships.
1482///
1483/// Read from the code, never from the message. The message is localised, the
1484/// code is not, and every attempt to tell these apart by reading English is
1485/// wrong the first time a server runs in another language.
1486fn database_error(error: sqlx::Error) -> async_graphql::Error {
1487    let sqlx::Error::Database(db) = &error else {
1488        return async_graphql::Error::new(error.to_string());
1489    };
1490    let described = match db.code().as_deref() {
1491        // Class 23, integrity constraint violation: the four PostgreSQL
1492        // raises and Hasura names.
1493        Some("23505") => "Uniqueness violation. ",
1494        Some("23502") => "Not-NULL violation. ",
1495        Some("23503") => "Foreign key violation. ",
1496        Some("23514") => "Check constraint violation. ",
1497        // Everything else is answered with PostgreSQL's own words and no
1498        // heading. Inventing one for a class Hasura says nothing about would
1499        // be a difference rather than a match.
1500        _ => "",
1501    };
1502    let error = async_graphql::Error::new(format!("{}{}", described, db.message()));
1503    // And the code, where the SQLSTATE says which one. Only the codes Hasura's
1504    // corpus pins are set: everything else is left for `code_for` to classify,
1505    // which is a guess from the message text and says so. Replacing that guess
1506    // wholesale would be the same guess with fewer places to notice it.
1507    let coded = match db.code().as_deref() {
1508        Some(code) if code.starts_with("23") => Some("constraint-violation"),
1509        // A `LIKE` pattern that ends mid-escape. Not a data exception to
1510        // Hasura -- the pattern came from the request, so the request is what
1511        // was wrong.
1512        Some("22025") => Some("bad-request"),
1513        // A negative `OFFSET`, and the rest of class 22 with it.
1514        Some(code) if code.starts_with("22") => Some("data-exception"),
1515        _ => None,
1516    };
1517    match coded {
1518        Some(code) => at_code(error, code),
1519        None => error,
1520    }
1521}
1522
1523/// The same error, coded as Hasura codes it.
1524fn at_code(mut error: async_graphql::Error, code: &'static str) -> async_graphql::Error {
1525    let mut extensions = error.extensions.take().unwrap_or_default();
1526    extensions.set("code", code);
1527    error.extensions = Some(extensions);
1528    error
1529}
1530
1531/// The same error, told where in the request it happened.
1532///
1533/// Hasura names the place a write went wrong inside the *arguments* --
1534/// `$.selectionSet.insert_author.args.objects[0].bio` -- and a response path
1535/// cannot say that: it names fields of the answer, and the answer has no
1536/// `objects`. So the path is written where the error is raised, by whoever
1537/// knows which row and which column it is about, and
1538/// [`crate::hasura::path_for`] prefers it over the response path.
1539fn at_path(mut error: async_graphql::Error, path: &str) -> async_graphql::Error {
1540    let mut extensions = error.extensions.take().unwrap_or_default();
1541    extensions.set("path", path);
1542    error.extensions = Some(extensions);
1543    error
1544}
1545
1546fn create_object_type(obj: &TableObjectType, relationships: &[RelationshipField]) -> Object {
1547    let mut object = Object::new(&obj.name);
1548
1549    // A table with no comment still gets a description, because Hasura gives
1550    // one and a client generating documentation from the schema would
1551    // otherwise show a blank where it used to show this.
1552    object = match obj.description() {
1553        // An empty description is one that was given and is empty: metadata
1554        // said the type has none, which is not the same as saying nothing.
1555        Some("") => object,
1556        Some(desc) => object.description(desc),
1557        None => object.description(format!(
1558            "columns and relationships of \"{}\"",
1559            obj.table_name()
1560        )),
1561    };
1562
1563    // A GraphQL object may not have two fields of one name, and a schema that
1564    // tries to build one aborts the process rather than returning an error.
1565    // Relationship names are derived from the table they point at, so a
1566    // foreign key column named after its own target -- `pizza.crust`
1567    // referencing `crust`, which is an ordinary way to write that schema --
1568    // produces exactly that clash.
1569    //
1570    // The column wins. It is the table's own data, it is what a client's
1571    // existing queries select, and dropping it to keep a derived name would
1572    // lose something the schema actually says. The relationship is left out
1573    // and said so, which is what Hasura does with the same clash: the
1574    // metadata is marked inconsistent and the field is simply not there.
1575    let mut taken: HashSet<String> = obj.fields.iter().map(|f| f.name.clone()).collect();
1576
1577    // Collected rather than added as they are built, so they can go on in name
1578    // order -- which is the order Hasura answers introspection in, and does not
1579    // depend on which column the catalogue happened to list first.
1580    let mut fields: Vec<(String, Field)> = Vec::new();
1581
1582    for field in &obj.fields {
1583        let field_name = field.name.clone();
1584        let field_type = graphql_type_ref(&field.type_string());
1585        // A document-valued column can be asked for one part of itself, the
1586        // same way `#>` reads one. It is answered here rather than in SQL
1587        // because the same column may be asked for under several aliases --
1588        // `c32_json(path: "a")` beside `c32_json(path: "arr[0]")` -- and
1589        // one projection cannot carry both.
1590        let takes_path = matches!(&field.graphql_type, crate::types::GraphQLType::Json)
1591            || matches!(
1592                &field.graphql_type,
1593                crate::types::GraphQLType::Custom(name) if name == "json"
1594            );
1595
1596        // Create field with resolver that extracts from parent async_graphql::Value
1597        // The query resolver stores rows as FieldValue::value(Value::Object)
1598        // so we use as_value() to get the Value and extract fields from the Object
1599        let gql_field = Field::new(&field.name, field_type, move |ctx| {
1600            let field_name = field_name.clone();
1601            FieldFuture::new(async move {
1602                // Get the parent value as async_graphql::Value using as_value()
1603                if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1604                    // Convert field name to async_graphql::Name for lookup
1605                    let key = async_graphql::Name::new(&field_name);
1606                    let path = match takes_path {
1607                        true => ctx.args.get("path").and_then(|v| v.string().ok()),
1608                        false => None,
1609                    };
1610                    match map.get(&key) {
1611                        // A null is the answer, not a value to resolve. An
1612                        // enum-typed column said so with `internal: invalid
1613                        // item for enum` rather than answering null, because a
1614                        // null is not one of its members.
1615                        Some(Value::Null) => return Ok(None),
1616                        Some(val) => {
1617                            let val = match path {
1618                                Some(path) => walk_json_path(val, path)?,
1619                                None => val.clone(),
1620                            };
1621                            return match val {
1622                                Value::Null => Ok(None),
1623                                val => Ok(Some(FieldValue::value(val))),
1624                            };
1625                        }
1626                        None => {}
1627                    }
1628                }
1629
1630                // Field not found or parent not a Value::Object
1631                Ok(None)
1632            })
1633        });
1634
1635        let gql_field = match takes_path {
1636            true => gql_field.argument(
1637                InputValue::new("path", TypeRef::named("String")).description("JSON select path"),
1638            ),
1639            false => gql_field,
1640        };
1641
1642        // A computed field whose function takes more than the row: the
1643        // caller writes those under `args`, for the reason a computed
1644        // relationship's are written there.
1645        let gql_field = match field.arguments.is_empty() {
1646            true => gql_field,
1647            false => gql_field.argument(InputValue::new(
1648                "args",
1649                TypeRef::named_nn(computed_args_type_name(&obj.name, &field.name)),
1650            )),
1651        };
1652
1653        let gql_field = if let Some(desc) = &field.description {
1654            gql_field.description(desc)
1655        } else {
1656            gql_field
1657        };
1658
1659        fields.push((field.name.clone(), gql_field));
1660    }
1661
1662    // Relationship fields. The query resolver embeds related rows into the
1663    // parent JSON before returning it, so these read from the parent value the
1664    // same way column fields do.
1665    for rel in relationships {
1666        if !taken.insert(rel.name.clone()) {
1667            tracing::warn!(
1668                "{}: not exposing the relationship to \"{}\" as `{}` -- the table \
1669                 already has a column of that name",
1670                obj.name,
1671                rel.target_type,
1672                rel.name
1673            );
1674            continue;
1675        }
1676        let field_name = rel.name.clone();
1677        let field_type = if rel.is_list {
1678            TypeRef::named_nn_list_nn(&rel.target_type)
1679        } else if always_present(rel, &obj.table) {
1680            TypeRef::named_nn(&rel.target_type)
1681        } else {
1682            TypeRef::named(&rel.target_type)
1683        };
1684
1685        let is_list = rel.is_list;
1686        let mut gql_field = Field::new(&rel.name, field_type, move |ctx| {
1687            let field_name = field_name.clone();
1688            FieldFuture::new(async move {
1689                if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1690                    let key = async_graphql::Name::new(&field_name);
1691                    match map.get(&key) {
1692                        // A row that points at nothing has no related row, and
1693                        // the answer to that is null -- not an object whose
1694                        // every field is null, which is what handing a null
1695                        // parent value to the field resolvers produced.
1696                        Some(Value::Null) if !is_list => return Ok(None),
1697                        Some(val) => return Ok(Some(FieldValue::value(val.clone()))),
1698                        None => {}
1699                    }
1700                }
1701                Ok(None)
1702            })
1703        });
1704
1705        // The same arguments the root field takes. An embedded list is a list
1706        // like any other: a client showing the five most recent articles of an
1707        // author has nowhere else to say so, and without these the only way to
1708        // narrow one is to fetch all of it and discard the rest in the client.
1709        // A to-one relationship is left alone -- there is nothing to order or
1710        // page through when the answer is one row.
1711        // A computed relationship's function may take more than the row --
1712        // "the articles of this author matching a search" -- and `args` is
1713        // where the caller writes them, so a term called `limit` cannot shadow
1714        // the one that pages the result.
1715        if !rel.arguments.is_empty() {
1716            gql_field = gql_field.argument(InputValue::new(
1717                "args",
1718                TypeRef::named_nn(computed_args_type_name(&obj.name, &rel.name)),
1719            ));
1720        }
1721        if rel.is_list {
1722            gql_field = gql_field
1723                .argument(InputValue::new(
1724                    "distinct_on",
1725                    TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1726                        &rel.target_type,
1727                    )),
1728                ))
1729                .argument(InputValue::new(
1730                    "where",
1731                    TypeRef::named(crate::input::bool_exp::bool_exp_type_name(&rel.target_type)),
1732                ))
1733                .argument(InputValue::new(
1734                    "order_by",
1735                    TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1736                        &rel.target_type,
1737                    )),
1738                ))
1739                .argument(InputValue::new("limit", TypeRef::named("Int")))
1740                .argument(InputValue::new("offset", TypeRef::named("Int")));
1741        }
1742
1743        let gql_field = if let Some(desc) = &rel.description {
1744            gql_field.description(desc)
1745        } else {
1746            gql_field
1747        };
1748
1749        fields.push((rel.name.clone(), gql_field));
1750
1751        // `author { articles_aggregate { aggregate { count } } }` -- the count
1752        // of a row's children without fetching them, which is the query behind
1753        // every "12 comments" beside a post. Only for a relationship to many:
1754        // counting one row is not a question anyone asks.
1755        if rel.is_list {
1756            let aggregate_field = format!("{}_aggregate", rel.name);
1757            if taken.insert(aggregate_field.clone()) {
1758                let key = aggregate_field.clone();
1759                fields.push((
1760                    aggregate_field.clone(),
1761                    Field::new(
1762                        &aggregate_field,
1763                        TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
1764                            &rel.target_type,
1765                        )),
1766                        move |ctx| {
1767                            let key = key.clone();
1768                            FieldFuture::new(async move {
1769                                // Absent where the parent has no children at
1770                                // all; the aggregate type's own resolvers read
1771                                // a missing key as zero and an empty list.
1772                                Ok(Some(FieldValue::value(
1773                                    child_value(&ctx, &key).unwrap_or(Value::Null),
1774                                )))
1775                            })
1776                        },
1777                    )
1778                    // The same arguments the list itself takes. "How many of
1779                    // this author's articles were published this year" is the
1780                    // question a count is usually asked as, and without a
1781                    // `where` there is no way to write it.
1782                    .argument(InputValue::new(
1783                        "distinct_on",
1784                        TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1785                            &rel.target_type,
1786                        )),
1787                    ))
1788                    .argument(InputValue::new(
1789                        "where",
1790                        TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1791                            &rel.target_type,
1792                        )),
1793                    ))
1794                    .argument(InputValue::new(
1795                        "order_by",
1796                        TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1797                            &rel.target_type,
1798                        )),
1799                    ))
1800                    .argument(InputValue::new("limit", TypeRef::named("Int")))
1801                    .argument(InputValue::new("offset", TypeRef::named("Int")))
1802                    .description(format!("Aggregates over {}.", rel.name)),
1803                ));
1804                // Counting the rows a function answers with means calling it,
1805                // so the aggregate takes whatever the function takes.
1806                if !rel.arguments.is_empty() {
1807                    let last = fields.len() - 1;
1808                    let (name, field) = fields.remove(last);
1809                    fields.push((
1810                        name,
1811                        field.argument(InputValue::new(
1812                            "args",
1813                            TypeRef::named_nn(computed_args_type_name(&obj.name, &rel.name)),
1814                        )),
1815                    ));
1816                }
1817            }
1818        }
1819    }
1820
1821    fields.sort_by(|(a, _), (b, _)| a.cmp(b));
1822    for (_, field) in fields {
1823        object = object.field(field);
1824    }
1825
1826    object
1827}
1828
1829/// A root field, whichever root it is on.
1830///
1831/// A query field and a subscription field take the same arguments and mean the
1832/// same by them -- the difference is only whether the answer is sent once or
1833/// whenever it changes -- and the two builders share no trait of their own, so
1834/// this is what lets the argument list be written once.
1835trait RootField: Sized {
1836    fn with_argument(self, value: InputValue) -> Self;
1837}
1838
1839impl RootField for Field {
1840    fn with_argument(self, value: InputValue) -> Self {
1841        self.argument(value)
1842    }
1843}
1844
1845impl RootField for SubscriptionField {
1846    fn with_argument(self, value: InputValue) -> Self {
1847        self.argument(value)
1848    }
1849}
1850
1851/// The five arguments that narrow a set of rows, in the order Hasura lists
1852/// them.
1853fn with_row_arguments<F: RootField>(field: F, type_name: &str) -> F {
1854    with_row_arguments_named(field, type_name, true)
1855}
1856
1857/// The same, where `columns` says whether the table has any to name.
1858///
1859/// A table a role may count and not read has none: there is no column enum to
1860/// order by and none to be distinct on, so those two arguments are not there
1861/// -- and could not be, since the types they name are not built either. What
1862/// is left still narrows the set being counted, which is the whole of what
1863/// such a root is for.
1864fn with_row_arguments_named<F: RootField>(field: F, type_name: &str, columns: bool) -> F {
1865    let mut field = field;
1866    if columns {
1867        field = field.with_argument(InputValue::new(
1868            "distinct_on",
1869            TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(type_name)),
1870        ));
1871    }
1872    field = field
1873        .with_argument(InputValue::new("limit", TypeRef::named("Int")))
1874        .with_argument(InputValue::new("offset", TypeRef::named("Int")));
1875    if columns {
1876        field = field.with_argument(InputValue::new(
1877            "order_by",
1878            TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(type_name)),
1879        ));
1880    }
1881    field.with_argument(InputValue::new(
1882        "where",
1883        TypeRef::named(crate::input::bool_exp::bool_exp_type_name(type_name)),
1884    ))
1885}
1886
1887/// One argument per primary key column, named and typed after the column
1888/// itself rather than assuming an integer `id`.
1889fn with_key_arguments<F: RootField>(
1890    field: F,
1891    schema_name: &str,
1892    table_name: &str,
1893    pk_columns: &[(String, String)],
1894    names: &crate::names::NameOverrides,
1895) -> F {
1896    let mut field = field;
1897    for (column, pg_type) in pk_columns {
1898        field = field.with_argument(InputValue::new(
1899            names
1900                .column(schema_name, table_name, column)
1901                .unwrap_or(column),
1902            TypeRef::named_nn(pk_argument_type(pg_type)),
1903        ));
1904    }
1905    field
1906}
1907
1908/// Create the Query type with all table query fields.
1909fn create_query_type(
1910    generated: &GeneratedSchema,
1911    max_rows: Option<i64>,
1912    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
1913    names: Arc<crate::names::NameOverrides>,
1914) -> Object {
1915    // Collected rather than added as they are built, so they can go on in name
1916    // order: that is the order Hasura answers introspection in, and it is the
1917    // order a client diffing two schemas, or a generator writing its types
1918    // out, gets a stable answer from.
1919    let mut roots: Vec<(String, Field)> = Vec::new();
1920
1921    for field in &generated.query_fields {
1922        let table_name = field.table_name.clone();
1923        let schema_name = field.schema_name.clone();
1924        let type_name = field.type_name.clone();
1925        let is_by_pk = field.is_by_pk;
1926        let pk_columns = field.pk_columns.clone();
1927        let return_type = graphql_type_ref(&field.return_type);
1928
1929        let spec_type_name = type_name.clone();
1930        let spec = Arc::new(QueryFieldSpec {
1931            schema_name,
1932            table_name,
1933            type_name,
1934            is_by_pk,
1935            pk_columns: pk_columns.clone(),
1936            max_rows,
1937            relationships: Arc::clone(&relationships),
1938            names: Arc::clone(&names),
1939            call: None,
1940        });
1941
1942        let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
1943            let spec = Arc::clone(&spec);
1944            FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
1945        });
1946
1947        // Add standard query arguments
1948        gql_field = match is_by_pk {
1949            false => with_row_arguments(gql_field, &spec_type_name),
1950            true => with_key_arguments(
1951                gql_field,
1952                &field.schema_name,
1953                &field.table_name,
1954                &pk_columns,
1955                names.as_ref(),
1956            ),
1957        };
1958
1959        if let Some(desc) = &field.description {
1960            gql_field = gql_field.description(desc);
1961        }
1962
1963        // A table this role may count and not read has no row type, so the
1964        // list root that would answer with one is not built. The entry is
1965        // still here for the aggregate root below, which is what such a role
1966        // was granted.
1967        if field.rows {
1968            roots.push((field.name.clone(), gql_field));
1969        }
1970
1971        // The same rows, with numbers about them. Same arguments as the list
1972        // field, because `author_aggregate(where: ...)` counts the set the
1973        // filter describes, not the whole table.
1974        //
1975        // Not built at all for a role whose permission withholds it: counting
1976        // rows is a way of learning about them, so it is granted separately
1977        // from reading them and refused by absence rather than at execution.
1978        if !is_by_pk && field.aggregates {
1979            let agg_spec = Arc::new(AggregateSpec {
1980                schema_name: field.schema_name.clone(),
1981                table_name: field.table_name.clone(),
1982                type_name: field.type_name.clone(),
1983                max_rows,
1984                relationships: Arc::clone(&relationships),
1985                names: Arc::clone(&names),
1986                call: None,
1987            });
1988            let aggregate_field_name = field
1989                .aggregate_name
1990                .clone()
1991                .unwrap_or_else(|| crate::schema::aggregate::aggregate_type_name(&field.type_name));
1992            let aggregate_field_name_for_sorting = aggregate_field_name.clone();
1993            let mut agg_field = Field::new(
1994                aggregate_field_name,
1995                TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
1996                    &field.type_name,
1997                )),
1998                move |ctx| {
1999                    let agg_spec = Arc::clone(&agg_spec);
2000                    FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
2001                },
2002            );
2003            agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2004            agg_field = match field.aggregate_description.as_deref() {
2005                Some("") => agg_field,
2006                Some(given) => agg_field.description(given),
2007                None => agg_field.description(format!(
2008                    "fetch aggregated fields from the table: \"{}\"",
2009                    field.table_name
2010                )),
2011            };
2012            roots.push((aggregate_field_name_for_sorting, agg_field));
2013        }
2014    }
2015
2016    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2017    let mut query = Object::new("query_root");
2018    // A GraphQL object may not have no fields, so a schema that exposes no
2019    // table needs something on its query root. Hasura puts a placeholder there
2020    // and calls it this; a client that reaches it has nothing to read.
2021    if roots.is_empty() {
2022        return query.field(
2023            Field::new("no_queries_available", TypeRef::named("String"), |_| {
2024                FieldFuture::new(async move { Ok(None::<Value>) })
2025            })
2026            .description("There are no queries available to the current role."),
2027        );
2028    }
2029    for (_, field) in roots {
2030        query = query.field(field);
2031    }
2032
2033    query
2034}
2035
2036/// One document operator: its name, and the type its columns are given.
2037type JsonbOperator = (&'static str, fn() -> TypeRef);
2038
2039/// What a document column may be told to do, and what each is told with.
2040///
2041/// `_append` and `_prepend` take another document; `_delete_key` takes a key;
2042/// `_delete_elem` takes an index into an array; `_delete_at_path` takes the
2043/// path to what is being removed. The shapes are per operator rather than per
2044/// column, which is why one input type per operator says all of it.
2045const JSONB_OPERATORS: &[JsonbOperator] = &[
2046    ("_append", || TypeRef::named("jsonb")),
2047    ("_delete_at_path", || TypeRef::named_nn_list("String")),
2048    ("_delete_elem", || TypeRef::named("Int")),
2049    ("_delete_key", || TypeRef::named("String")),
2050    ("_prepend", || TypeRef::named("jsonb")),
2051];
2052
2053/// The name of the input holding one document operator's columns.
2054fn jsonb_operator_input(type_name: &str, operator: &str) -> String {
2055    format!("{}{}_input", type_name, operator)
2056}
2057
2058/// Add the operators an update may be written with.
2059///
2060/// All optional, and at least one required -- which GraphQL cannot express, so
2061/// the resolver says so instead of the schema. Making `_set` non-null would
2062/// have been expressible and wrong: an update that only increments a counter
2063/// never sends one.
2064fn with_update_operators(
2065    field: Field,
2066    base_name: &str,
2067    has_numeric: bool,
2068    has_jsonb: bool,
2069) -> Field {
2070    let mut field = field;
2071    // A table with no document column is not offered the document operators:
2072    // there is nothing there to append to.
2073    if has_jsonb {
2074        for (operator, _) in JSONB_OPERATORS {
2075            field = field.argument(InputValue::new(
2076                *operator,
2077                TypeRef::named(jsonb_operator_input(base_name, operator)),
2078            ));
2079        }
2080    }
2081    if has_numeric {
2082        field = field.argument(InputValue::new(
2083            "_inc",
2084            TypeRef::named(format!("{}_inc_input", base_name)),
2085        ));
2086    }
2087    field.argument(InputValue::new(
2088        "_set",
2089        TypeRef::named(format!("{}_set_input", base_name)),
2090    ))
2091}
2092
2093/// Create the Mutation type with all mutation fields.
2094/// Which columns a write may name, and so which of its inputs exist.
2095///
2096/// One definition, called both where the inputs are registered and where the
2097/// arguments naming them are declared. They used to agree by coincidence --
2098/// both read the object type's fields -- and stopped the moment a write input
2099/// began following its own permission. An argument naming a type nobody
2100/// registered is a schema async-graphql refuses to build, which costs a role
2101/// every field it has over the one it cannot use.
2102fn write_fields<'a>(
2103    object: &'a crate::schema::object::TableObjectType,
2104    names: &crate::names::NameOverrides,
2105    role: Option<&str>,
2106    verb: crate::role::Verb,
2107) -> Vec<&'a crate::schema::object::GraphQLField> {
2108    let allowed = role.and_then(|role| {
2109        names
2110            .permissions(&object.table.schema, &object.table.name, role)
2111            .and_then(|granted| granted.write_columns(verb))
2112    });
2113    object
2114        .writable_fields
2115        .iter()
2116        .filter(|field| {
2117            let column = table_column_for(names, &object.table, &field.name);
2118            // A column PostgreSQL generates always is not one a write may
2119            // name. Leaving it in the input type moved the refusal to the
2120            // database, which answers `cannot insert a non-DEFAULT value into
2121            // column "id"` and has by then forgotten which argument the value
2122            // came from. Out of the type, the walk over the request says
2123            // `field 'id' not found in type: 'author_insert_input'` and can
2124            // still point at it.
2125            object
2126                .table
2127                .get_column(column)
2128                .is_some_and(|c| !c.always_generated)
2129                && allowed.is_none_or(|set| set.allows(column))
2130        })
2131        .collect()
2132}
2133
2134/// Whether a type has an `_inc_input` and a set of document operator inputs.
2135fn update_inputs(
2136    object: &crate::schema::object::TableObjectType,
2137    names: &crate::names::NameOverrides,
2138    role: Option<&str>,
2139) -> (bool, bool) {
2140    let fields = write_fields(object, names, role, crate::role::Verb::Update);
2141    (
2142        fields
2143            .iter()
2144            .any(|f| crate::schema::aggregate::is_incrementable(&f.graphql_type)),
2145        fields
2146            .iter()
2147            .any(|f| matches!(&f.graphql_type, crate::types::GraphQLType::Json)),
2148    )
2149}
2150
2151fn create_mutation_type(
2152    generated: &GeneratedSchema,
2153    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2154    type_names: Arc<HashMap<(String, String), String>>,
2155    names: Arc<crate::names::NameOverrides>,
2156    max_rows: Option<i64>,
2157    role: Option<&str>,
2158) -> Object {
2159    let mut mutation = Object::new("mutation_root");
2160    // In name order, for the reason the query root is: see there.
2161    let mut roots: Vec<(String, Field)> = Vec::new();
2162
2163    // Only a table with a unique constraint has a conflict to name, and only
2164    // those got the types for it.
2165    // Which tables have something to add to, and which have a document to
2166    // operate on. Read from what was registered above rather than worked out
2167    // again from the object types: the second answer is the one that can
2168    // drift, and drifting means an argument naming a type that is not there.
2169    let mut has_numeric_column: HashSet<String> = HashSet::new();
2170    let mut has_jsonb_column: HashSet<String> = HashSet::new();
2171    for (type_name, object) in &generated.object_types {
2172        let (numeric, jsonb) = update_inputs(object, &names, role);
2173        if numeric {
2174            has_numeric_column.insert(type_name.clone());
2175        }
2176        if jsonb {
2177            has_jsonb_column.insert(type_name.clone());
2178        }
2179    }
2180
2181    let has_conflict_target: HashSet<String> = generated
2182        .object_types
2183        .iter()
2184        .filter(|(_, object)| !object.table.unique_constraints.is_empty())
2185        .map(|(type_name, _)| type_name.clone())
2186        .collect();
2187
2188    for field in &generated.mutation_fields {
2189        let table_name = field.table_name.clone();
2190        let schema_name = field.schema_name.clone();
2191        let mutation_type = field.mutation_type;
2192        let pk_columns = field.pk_columns.clone();
2193        let return_type = graphql_type_ref(&field.return_type);
2194
2195        // The table's own type name, which is what its boolean expression is
2196        // named after. A bulk mutation returns `[author!]!` and a by-key one
2197        // returns `author`; both name the same table.
2198        let where_type = field
2199            .return_type
2200            .trim_matches(|c| c == '[' || c == ']' || c == '!')
2201            .trim_end_matches("_mutation_response")
2202            .to_string();
2203
2204        let resolver_pk_columns = pk_columns.clone();
2205        let field_relationships = Arc::clone(&relationships);
2206        let field_type_names = Arc::clone(&type_names);
2207        let field_names = Arc::clone(&names);
2208        let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2209            let table_name = table_name.clone();
2210            let schema_name = schema_name.clone();
2211            let pk_columns = resolver_pk_columns.clone();
2212            let relationships = Arc::clone(&field_relationships);
2213            let type_names = Arc::clone(&field_type_names);
2214            let names = Arc::clone(&field_names);
2215            FieldFuture::new(async move {
2216                resolve_mutation(
2217                    &ctx,
2218                    &schema_name,
2219                    &table_name,
2220                    mutation_type,
2221                    &pk_columns,
2222                    &relationships,
2223                    &type_names,
2224                    &names,
2225                    max_rows,
2226                )
2227                .await
2228            })
2229        });
2230
2231        // Add mutation-specific arguments.
2232        //
2233        // A by-PK mutation takes the key columns rather than a `where` object:
2234        // it is meant to address exactly one row, and accepting `where` made it
2235        // an ordinary bulk mutation that happened to return the first result.
2236        match mutation_type {
2237            MutationType::Insert | MutationType::InsertOne => {
2238                let insert_input = format!("{}_insert_input", where_type);
2239                gql_field = if mutation_type == MutationType::Insert {
2240                    gql_field.argument(InputValue::new(
2241                        "objects",
2242                        TypeRef::named_nn_list_nn(&insert_input),
2243                    ))
2244                } else {
2245                    // A single insert takes `object`, not a one-element
2246                    // `objects`.
2247                    gql_field.argument(InputValue::new("object", TypeRef::named_nn(&insert_input)))
2248                };
2249                if has_conflict_target.contains(&where_type) {
2250                    gql_field = gql_field.argument(InputValue::new(
2251                        "on_conflict",
2252                        TypeRef::named(format!("{}_on_conflict", where_type)),
2253                    ));
2254                }
2255            }
2256            MutationType::UpdateByPk => {
2257                gql_field = gql_field.argument(InputValue::new(
2258                    "pk_columns",
2259                    TypeRef::named_nn(format!("{}_pk_columns_input", where_type)),
2260                ));
2261                gql_field = with_update_operators(
2262                    gql_field,
2263                    &where_type,
2264                    has_numeric_column.contains(&where_type),
2265                    has_jsonb_column.contains(&where_type),
2266                );
2267            }
2268            MutationType::Update => {
2269                gql_field = gql_field.argument(InputValue::new(
2270                    "where",
2271                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2272                ));
2273                gql_field = with_update_operators(
2274                    gql_field,
2275                    &where_type,
2276                    has_numeric_column.contains(&where_type),
2277                    has_jsonb_column.contains(&where_type),
2278                );
2279            }
2280            MutationType::UpdateMany => {
2281                gql_field = gql_field.argument(InputValue::new(
2282                    "updates",
2283                    TypeRef::named_nn_list_nn(format!("{}_updates", where_type)),
2284                ));
2285            }
2286            MutationType::DeleteByPk => {
2287                for (col_name, pg_type) in &pk_columns {
2288                    gql_field = gql_field.argument(InputValue::new(
2289                        names
2290                            .column(&field.schema_name, &field.table_name, col_name)
2291                            .unwrap_or(col_name),
2292                        TypeRef::named_nn(pk_argument_type(pg_type)),
2293                    ));
2294                }
2295            }
2296            MutationType::Delete => {
2297                // Required, as it is on an update: a delete with no predicate
2298                // is a delete of the whole table, and this refused one at
2299                // execution while the schema said it was a query worth
2300                // writing. Saying so in the type is what Hasura does, and it
2301                // is what a client's own tooling can catch before the request
2302                // is sent.
2303                gql_field = gql_field.argument(InputValue::new(
2304                    "where",
2305                    TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2306                ));
2307            }
2308        }
2309
2310        if let Some(desc) = &field.description {
2311            gql_field = gql_field.description(desc);
2312        }
2313
2314        roots.push((field.name.clone(), gql_field));
2315    }
2316
2317    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2318    for (_, field) in roots {
2319        mutation = mutation.field(field);
2320    }
2321
2322    mutation
2323}
2324
2325/// Create the Subscription type: the query root, answered again on change.
2326///
2327/// A subscription here is a **live query**. It carries the same fields the
2328/// query root does, takes the same arguments, and answers with the same rows
2329/// -- the difference is that the answer is sent again whenever it stops being
2330/// true. That is the contract a client generated against Hasura expects, and
2331/// the reason its `subscription_root` is a mirror of its `query_root`.
2332///
2333/// What wakes it is this server's own: a trigger publishes on the changed
2334/// table and the query is read again, which costs nothing while nothing is
2335/// written and answers in milliseconds when something is. A trigger cannot
2336/// see everything a query can -- a view has none, an embedded row may live in
2337/// a table that carries none, and `where: {expires_at: {_lt: "now()"}}`
2338/// changes with no write at all -- so a slow refresh runs beside it and
2339/// closes exactly those gaps. Polling alone is what Hasura does, and it pays
2340/// for every subscriber every tick whether or not anything happened.
2341fn create_subscription_type(
2342    generated: &GeneratedSchema,
2343    max_rows: Option<i64>,
2344    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2345    names: Arc<crate::names::NameOverrides>,
2346    refresh: std::time::Duration,
2347) -> Subscription {
2348    let mut roots: Vec<(String, SubscriptionField)> = Vec::new();
2349
2350    for field in &generated.query_fields {
2351        let spec_type_name = field.type_name.clone();
2352        let pk_columns = field.pk_columns.clone();
2353        let is_by_pk = field.is_by_pk;
2354        let return_type = graphql_type_ref(&field.return_type);
2355        let watched = vec![crate::subscription::table_channel_name(
2356            &field.schema_name,
2357            &field.table_name,
2358        )];
2359
2360        let spec = Arc::new(QueryFieldSpec {
2361            schema_name: field.schema_name.clone(),
2362            table_name: field.table_name.clone(),
2363            type_name: field.type_name.clone(),
2364            is_by_pk,
2365            pk_columns: pk_columns.clone(),
2366            max_rows,
2367            relationships: Arc::clone(&relationships),
2368            names: Arc::clone(&names),
2369            call: None,
2370        });
2371
2372        let mut gql_field = SubscriptionField::new(&field.name, return_type, move |ctx| {
2373            let spec = Arc::clone(&spec);
2374            let watched = watched.clone();
2375            SubscriptionFieldFuture::new(async move {
2376                let wake = wake_on(&ctx, &watched, refresh).await;
2377                Ok(live(ctx, LiveSource::Rows(spec), wake))
2378            })
2379        });
2380
2381        gql_field = match is_by_pk {
2382            false => with_row_arguments(gql_field, &spec_type_name),
2383            true => with_key_arguments(
2384                gql_field,
2385                &field.schema_name,
2386                &field.table_name,
2387                &pk_columns,
2388                names.as_ref(),
2389            ),
2390        };
2391        if let Some(desc) = &field.description {
2392            gql_field = gql_field.description(desc);
2393        }
2394        // No row type, no root that answers with one -- see the query root.
2395        if field.rows {
2396            roots.push((field.name.clone(), gql_field));
2397        }
2398
2399        // A live query mirrors the query root, so an aggregate the role may not
2400        // ask for once is not one it may ask for continuously either.
2401        if is_by_pk || !field.aggregates {
2402            continue;
2403        }
2404
2405        // The same rows, with numbers about them, watched the same way.
2406        let agg_spec = Arc::new(AggregateSpec {
2407            schema_name: field.schema_name.clone(),
2408            table_name: field.table_name.clone(),
2409            type_name: field.type_name.clone(),
2410            max_rows,
2411            relationships: Arc::clone(&relationships),
2412            names: Arc::clone(&names),
2413            call: None,
2414        });
2415        let agg_watched = vec![crate::subscription::table_channel_name(
2416            &field.schema_name,
2417            &field.table_name,
2418        )];
2419        let agg_name = field
2420            .aggregate_name
2421            .clone()
2422            .unwrap_or_else(|| crate::schema::aggregate::aggregate_type_name(&field.type_name));
2423        let mut agg_field = SubscriptionField::new(
2424            agg_name.clone(),
2425            TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2426                &field.type_name,
2427            )),
2428            move |ctx| {
2429                let agg_spec = Arc::clone(&agg_spec);
2430                let watched = agg_watched.clone();
2431                SubscriptionFieldFuture::new(async move {
2432                    let wake = wake_on(&ctx, &watched, refresh).await;
2433                    Ok(live(ctx, LiveSource::Aggregate(agg_spec), wake))
2434                })
2435            },
2436        );
2437        agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2438        agg_field = match field.aggregate_description.as_deref() {
2439            Some("") => agg_field,
2440            Some(given) => agg_field.description(given),
2441            None => agg_field.description(format!(
2442                "fetch aggregated fields from the table: \"{}\"",
2443                field.table_name
2444            )),
2445        };
2446        roots.push((agg_name, agg_field));
2447    }
2448
2449    roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2450    let mut subscription = Subscription::new("subscription_root");
2451    // A GraphQL object may not have no fields, and a schema that exposes no
2452    // table still has a subscription root if subscriptions are on.
2453    if roots.is_empty() {
2454        return subscription.field(SubscriptionField::new(
2455            "no_subscriptions_available",
2456            TypeRef::named("String"),
2457            |_| {
2458                SubscriptionFieldFuture::new(async move {
2459                    Ok(futures::stream::empty::<
2460                        Result<FieldValue, async_graphql::Error>,
2461                    >())
2462                })
2463            },
2464        ));
2465    }
2466    for (_, field) in roots {
2467        subscription = subscription.field(field);
2468    }
2469    subscription
2470}
2471
2472/// What tells a live query its answer may have changed.
2473///
2474/// Every notification on the tables it reads, and a slow tick beside them.
2475/// Yields once per wake and never ends -- a subscription lasts as long as the
2476/// client holds it open.
2477async fn wake_on(
2478    ctx: &ResolverContext<'_>,
2479    channels: &[String],
2480    refresh: std::time::Duration,
2481) -> futures::stream::BoxStream<'static, ()> {
2482    use futures::StreamExt;
2483
2484    let mut streams: Vec<futures::stream::BoxStream<'static, ()>> =
2485        Vec::with_capacity(channels.len() + 1);
2486    // A tick even where nothing is listening: a table with no trigger, a view,
2487    // and a predicate that changes with the clock all depend on it.
2488    streams.push(
2489        tokio_stream::wrappers::IntervalStream::new({
2490            let mut interval = tokio::time::interval(refresh);
2491            interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
2492            interval
2493        })
2494        .map(|_| ())
2495        .boxed(),
2496    );
2497
2498    if let Ok(broker_arc) = ctx.data::<Arc<RwLock<Option<NotifyBroker>>>>() {
2499        let guard = broker_arc.read().await;
2500        if let Some(broker) = guard.as_ref() {
2501            for channel in channels {
2502                // `subscribe_or_create` rather than `subscribe`: a table whose
2503                // trigger was never installed has no channel, and a live query
2504                // on it is answered by the refresh rather than refused.
2505                streams.push(
2506                    broker
2507                        .subscribe_or_create(channel)
2508                        .await
2509                        .map(|_| ())
2510                        .boxed(),
2511                );
2512            }
2513        }
2514    }
2515
2516    futures::stream::select_all(streams).boxed()
2517}
2518
2519/// What a live query reads each time it is woken.
2520///
2521/// An enum rather than a closure because the answer borrows the context it is
2522/// read from, and a closure returning a future that borrows its argument is
2523/// not something a bound can say.
2524enum LiveSource {
2525    Rows(Arc<QueryFieldSpec>),
2526    Aggregate(Arc<AggregateSpec>),
2527}
2528
2529/// Read one answer, and the value to compare it against the last one by.
2530async fn read_live<'a>(
2531    ctx: &ResolverContext<'a>,
2532    source: &LiveSource,
2533) -> Result<(serde_json::Value, Option<FieldValue<'a>>), async_graphql::Error> {
2534    match source {
2535        LiveSource::Rows(spec) => {
2536            let rows = query_rows(ctx, spec).await?;
2537            Ok((
2538                serde_json::Value::Array(rows.clone()),
2539                rows_as_field_value(rows, spec.is_by_pk),
2540            ))
2541        }
2542        LiveSource::Aggregate(spec) => {
2543            let value = aggregate_value(ctx, spec).await?;
2544            let seen = serde_json::to_value(&value).unwrap_or_default();
2545            Ok((seen, Some(FieldValue::value(value))))
2546        }
2547    }
2548}
2549
2550/// A live query: the answer now, and the answer again each time it changes.
2551///
2552/// The first item is sent as soon as the client subscribes, which is what
2553/// makes this a query rather than a feed -- there is no window in which the
2554/// client knows nothing. After that, `answer` is read again on every wake and
2555/// sent only when what came back is not what was sent last: a write that
2556/// changes a row the subscription does not select is a wake and not a message.
2557fn live<'a>(
2558    ctx: ResolverContext<'a>,
2559    source: LiveSource,
2560    wake: futures::stream::BoxStream<'static, ()>,
2561) -> impl futures::Stream<Item = Result<FieldValue<'a>, async_graphql::Error>> + Send + 'a {
2562    use futures::StreamExt;
2563
2564    struct Live<'a> {
2565        ctx: ResolverContext<'a>,
2566        source: LiveSource,
2567        wake: futures::stream::BoxStream<'static, ()>,
2568        sent: Option<serde_json::Value>,
2569        started: bool,
2570    }
2571
2572    futures::stream::unfold(
2573        Live {
2574            ctx,
2575            source,
2576            wake,
2577            sent: None,
2578            started: false,
2579        },
2580        |mut live: Live<'a>| async move {
2581            loop {
2582                if live.started {
2583                    live.wake.next().await?;
2584                } else {
2585                    live.started = true;
2586                }
2587                let read = read_live(&live.ctx, &live.source).await;
2588                match read {
2589                    // An error ends the subscription: the client asked
2590                    // something that cannot be answered, and answering it
2591                    // again on the next tick would only repeat that.
2592                    Err(e) => return Some((Err(e), live)),
2593                    Ok((seen, value)) => {
2594                        if live.sent.as_ref() == Some(&seen) {
2595                            continue;
2596                        }
2597                        live.sent = Some(seen);
2598                        let value = value.unwrap_or_else(|| FieldValue::value(Value::Null));
2599                        return Some((Ok(value), live));
2600                    }
2601                }
2602            }
2603        },
2604    )
2605    .take_while(|item| futures::future::ready(item.is_ok()))
2606}
2607
2608/// Everything a query field's resolver needs about the field it serves.
2609struct QueryFieldSpec {
2610    schema_name: String,
2611    table_name: String,
2612    type_name: String,
2613    is_by_pk: bool,
2614    pk_columns: Vec<(String, String)>,
2615    max_rows: Option<i64>,
2616    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2617    names: Arc<crate::names::NameOverrides>,
2618    /// Set where the rows come from a function rather than from the table
2619    /// itself. Everything else about reading them is the same.
2620    call: Option<Arc<FunctionCall>>,
2621}
2622
2623/// Add the root fields for functions that answer with rows of a table.
2624///
2625/// The same arguments a table's own root field takes, because the rows are the
2626/// same rows: what the function adds is `args`, which is where its own
2627/// arguments go so that a parameter called `limit` cannot shadow the one that
2628/// pages the result.
2629fn add_function_fields(
2630    mut root: Object,
2631    generated: &GeneratedSchema,
2632    volatile: bool,
2633    max_rows: Option<i64>,
2634    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2635    names: Arc<crate::names::NameOverrides>,
2636) -> Object {
2637    for function in &generated.function_fields {
2638        if function.volatile != volatile {
2639            continue;
2640        }
2641        let spec = Arc::new(QueryFieldSpec {
2642            schema_name: function.returns_table.0.clone(),
2643            table_name: function.returns_table.1.clone(),
2644            type_name: function.returns.clone(),
2645            is_by_pk: false,
2646            pk_columns: Vec::new(),
2647            max_rows,
2648            relationships: Arc::clone(&relationships),
2649            names: Arc::clone(&names),
2650            call: Some(Arc::new(FunctionCall {
2651                schema: function.schema_name.clone(),
2652                name: function.function_name.clone(),
2653                arguments: function.arguments.clone(),
2654                session_argument: function.session_argument.clone(),
2655            })),
2656        });
2657
2658        let mut field = Field::new(
2659            &function.name,
2660            TypeRef::named_nn_list_nn(&function.returns),
2661            move |ctx| {
2662                let spec = Arc::clone(&spec);
2663                FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
2664            },
2665        );
2666        if !function.arguments.is_empty() {
2667            field = field.argument(InputValue::new(
2668                "args",
2669                TypeRef::named_nn(format!("{}_args", function.name)),
2670            ));
2671        }
2672        field = field
2673            .argument(InputValue::new(
2674                "where",
2675                TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
2676                    &function.returns,
2677                )),
2678            ))
2679            .argument(InputValue::new(
2680                "order_by",
2681                TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
2682                    &function.returns,
2683                )),
2684            ))
2685            .argument(InputValue::new(
2686                "distinct_on",
2687                TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
2688                    &function.returns,
2689                )),
2690            ))
2691            .argument(InputValue::new("limit", TypeRef::named("Int")))
2692            .argument(InputValue::new("offset", TypeRef::named("Int")));
2693        if let Some(description) = &function.description {
2694            field = field.description(description);
2695        }
2696        root = root.field(field);
2697
2698        // The same rows, with numbers about them. Every table root has one and
2699        // so does every function root: `search_tracks_aggregate(args: {…})`
2700        // counts what `search_tracks(args: {…})` would have answered with,
2701        // which means calling the function and taking the same arguments.
2702        let agg_spec = Arc::new(AggregateSpec {
2703            schema_name: function.returns_table.0.clone(),
2704            table_name: function.returns_table.1.clone(),
2705            type_name: function.returns.clone(),
2706            max_rows,
2707            relationships: Arc::clone(&relationships),
2708            names: Arc::clone(&names),
2709            call: Some(Arc::new(FunctionCall {
2710                schema: function.schema_name.clone(),
2711                name: function.function_name.clone(),
2712                arguments: function.arguments.clone(),
2713                session_argument: function.session_argument.clone(),
2714            })),
2715        });
2716        let mut aggregate = Field::new(
2717            format!("{}_aggregate", function.name),
2718            TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2719                &function.returns,
2720            )),
2721            move |ctx| {
2722                let agg_spec = Arc::clone(&agg_spec);
2723                FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
2724            },
2725        );
2726        if !function.arguments.is_empty() {
2727            aggregate = aggregate.argument(InputValue::new(
2728                "args",
2729                TypeRef::named_nn(format!("{}_args", function.name)),
2730            ));
2731        }
2732        aggregate = with_row_arguments(aggregate, &function.returns).description(format!(
2733            "fetch aggregated fields from the table: \"{}\"",
2734            function.returns_table.1
2735        ));
2736        root = root.field(aggregate);
2737    }
2738    root
2739}
2740
2741/// The FROM entry for a root field that calls a function.
2742///
2743/// Aliased as the table the function answers with rows of, so a filter or an
2744/// ordering written against that table's columns finds them: without it the
2745/// FROM clause names the function and `article.score` is a table nobody
2746/// mentioned. Whatever the caller put under `args` is bound here, which is why
2747/// it takes the parameter list.
2748fn function_source(
2749    ctx: &ResolverContext<'_>,
2750    call: &FunctionCall,
2751    alias: &str,
2752    bound_values: &mut Vec<serde_json::Value>,
2753) -> Result<String, async_graphql::Error> {
2754    let given = ctx
2755        .args
2756        .try_get("args")
2757        .ok()
2758        .map(|v| accessor_to_json(&v))
2759        .unwrap_or_else(|| serde_json::json!({}));
2760    let mut passed = Vec::new();
2761    for (name, _, required) in &call.arguments {
2762        match given.get(name) {
2763            None if *required => {
2764                return Err(async_graphql::Error::new(format!(
2765                    "{} needs the argument \"{}\"",
2766                    call.name, name
2767                )))
2768            }
2769            // An argument left out is left out, so the function's own default
2770            // applies. Passing null instead would override it.
2771            None => continue,
2772            Some(value) => {
2773                // Named notation, so the arguments a client did give land on
2774                // the parameters it meant regardless of order. A GraphQL Int
2775                // binds as a bigint, and `f(article_id => bigint)` is not
2776                // `f(integer)` -- the function is looked up by the types of
2777                // what it was handed, so what it was handed has to be the
2778                // types it declares.
2779                let (_, pg_type, _) = call
2780                    .arguments
2781                    .iter()
2782                    .find(|(argument, _, _)| argument == name)
2783                    .expect("iterating the arguments");
2784                passed.push(format!(
2785                    "{} => ${}::{}",
2786                    postrust_sql::escape_ident(name),
2787                    bound_values.len() + 1,
2788                    pg_type
2789                ));
2790                bound_values.push(value.clone());
2791            }
2792        }
2793    }
2794    // The session argument is not the client's to send: it says who is
2795    // asking, and a caller that could write it could name any identity it
2796    // liked. It is filled here, from the verified token.
2797    if let Some(session) = &call.session_argument {
2798        passed.push(format!(
2799            "{} => {}",
2800            postrust_sql::escape_ident(session),
2801            SESSION_ARGUMENT
2802        ));
2803    }
2804    Ok(format!(
2805        "{}.{}({}) AS {}",
2806        postrust_sql::escape_ident(&call.schema),
2807        postrust_sql::escape_ident(&call.name),
2808        passed.join(", "),
2809        postrust_sql::escape_ident(alias)
2810    ))
2811}
2812
2813/// A function a root field calls instead of reading a table.
2814struct FunctionCall {
2815    schema: String,
2816    name: String,
2817    arguments: Vec<(String, String, bool)>,
2818    /// The parameter filled from the caller's session rather than from the
2819    /// request. See [`crate::schema::FunctionField::session_argument`].
2820    session_argument: Option<String>,
2821}
2822
2823/// Everything an aggregate field's resolver needs.
2824struct AggregateSpec {
2825    schema_name: String,
2826    table_name: String,
2827    type_name: String,
2828    max_rows: Option<i64>,
2829    relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2830    names: Arc<crate::names::NameOverrides>,
2831    /// Set where the rows come from a function rather than from the table
2832    /// itself, exactly as it is on [`QueryFieldSpec`]. Counting the rows a
2833    /// function answers with means calling it.
2834    call: Option<Arc<FunctionCall>>,
2835}
2836
2837/// Resolve an aggregate field.
2838///
2839/// One pass over the selection decides what SQL to write: a client asking only
2840/// for `count` should not pay for the rows, and one asking only for `nodes`
2841/// should not pay for a second scan. Both halves read from the same filtered
2842/// subquery, so `aggregate` describes exactly the set `nodes` came from.
2843async fn resolve_aggregate<'a>(
2844    ctx: &ResolverContext<'a>,
2845    spec: &AggregateSpec,
2846) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
2847    Ok(Some(FieldValue::value(aggregate_value(ctx, spec).await?)))
2848}
2849
2850/// The numbers an aggregate field answers with.
2851///
2852/// Split from the resolver for the reason [`query_rows`] is: a subscription
2853/// reads them over and over and has to tell one answer from the last.
2854async fn aggregate_value(
2855    ctx: &ResolverContext<'_>,
2856    spec: &AggregateSpec,
2857) -> Result<Value, async_graphql::Error> {
2858    use crate::schema::aggregate as agg;
2859
2860    let pool = ctx.data::<PgPool>()?;
2861    let gql_ctx = ctx.data::<GraphQLContext>()?;
2862
2863    let mut bound_values: Vec<serde_json::Value> = Vec::new();
2864    // A function's own arguments are bound before anything else, so a
2865    // predicate written beside them continues the numbering rather than
2866    // colliding with it.
2867    let source = match &spec.call {
2868        None => format!(
2869            "{}.{}",
2870            postrust_sql::escape_ident(&spec.schema_name),
2871            postrust_sql::escape_ident(&spec.table_name)
2872        ),
2873        Some(call) => function_source(ctx, call, &spec.table_name, &mut bound_values)?,
2874    };
2875
2876    let mut where_sql = String::new();
2877    {
2878        // Counting rows is reading them, so the permission applies here in the
2879        // same breath as the request's own filter -- otherwise `count` would
2880        // report a total the same role cannot list.
2881        let requested = ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v));
2882        let permission = permission_predicate(
2883            &gql_ctx.caller(),
2884            spec.names.as_ref(),
2885            &spec.schema_name,
2886            &spec.table_name,
2887        )?;
2888        if let Some(predicate) = and_predicates(requested, permission) {
2889            let guard = gql_ctx
2890                .schema_cache
2891                .get()
2892                .await
2893                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
2894            let cache = guard
2895                .as_ref()
2896                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
2897            // A function's rows are read under the table's name as an alias,
2898            // not from the table itself -- the same split the row read makes.
2899            // Counting them under the qualified name answered `invalid
2900            // reference to FROM-clause entry for table "Track"` the moment a
2901            // permission referred to a column, which is any permission that
2902            // follows a relationship.
2903            let scope = match &spec.call {
2904                None => WhereScope::table(
2905                    &spec.schema_name,
2906                    &spec.table_name,
2907                    &spec.type_name,
2908                    spec.names.as_ref(),
2909                )
2910                .with_resolution(cache, spec.relationships.as_ref()),
2911                Some(_) => WhereScope::for_alias(
2912                    &spec.schema_name,
2913                    &spec.table_name,
2914                    &spec.table_name,
2915                    &spec.type_name,
2916                    cache,
2917                    spec.relationships.as_ref(),
2918                    spec.names.as_ref(),
2919                ),
2920            }
2921            .for_caller(gql_ctx.caller());
2922            let (sql, values) =
2923                build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
2924            if !sql.is_empty() {
2925                where_sql = format!(" {}", sql);
2926                bound_values.extend(values);
2927            }
2928        }
2929    }
2930
2931    let order_sql = build_order_by_clause(
2932        ctx,
2933        &gql_ctx.schema_cache,
2934        &spec.schema_name,
2935        &spec.table_name,
2936        &spec.type_name,
2937        spec.relationships.as_ref(),
2938        &format!(
2939            "{}.{}",
2940            postrust_sql::escape_ident(&spec.schema_name),
2941            postrust_sql::escape_ident(&spec.table_name)
2942        ),
2943        spec.names.as_ref(),
2944    )
2945    .await?;
2946
2947    let requested_limit = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
2948    let offset = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
2949    let ceiling = match (
2950        spec.max_rows,
2951        permission_limit(
2952            &gql_ctx.caller(),
2953            spec.names.as_ref(),
2954            &spec.schema_name,
2955            &spec.table_name,
2956        ),
2957    ) {
2958        (Some(configured), Some(granted)) => Some(configured.min(granted)),
2959        (only, None) | (None, only) => only,
2960    };
2961    let limit = match (requested_limit, ceiling) {
2962        (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
2963        (Some(requested), None) => Some(requested),
2964        (None, ceiling) => ceiling,
2965    };
2966
2967    let rows = format!("SELECT * FROM {}{}{}", source, where_sql, order_sql);
2968
2969    // What `nodes` reads: the page, bounded by whichever ceiling is lowest.
2970    let mut inner = rows.clone();
2971    if let Some(limit) = limit {
2972        inner.push_str(&format!(" LIMIT {}", limit));
2973    }
2974    if let Some(offset) = offset {
2975        inner.push_str(&format!(" OFFSET {}", offset));
2976    }
2977
2978    // What `aggregate` reads, which is not the same rows. A count is not a
2979    // page: a ceiling exists to bound how many rows travel, and answering
2980    // "how many are there" with "as many as I would have sent you" is not an
2981    // answer to the question. Hasura says so too -- a role limited to one row
2982    // of `article` still counts three of them and still gets `max(id)` over
2983    // all three, which is what `agg_perm` tests.
2984    //
2985    // The request's own `limit` and `offset` do still apply: those were asked
2986    // for, and `article_aggregate(limit: 2)` is a question about two rows. The
2987    // corpus proves this only for the permission's ceiling; the configured one
2988    // is treated the same way because the reason is the same, and because a
2989    // server whose `PGRST_MAX_ROWS` silently became the answer to `count`
2990    // would be wrong in the way that is hardest to notice.
2991    let mut counted = rows;
2992    if let Some(limit) = requested_limit {
2993        counted.push_str(&format!(" LIMIT {}", limit));
2994    }
2995    if let Some(offset) = offset {
2996        counted.push_str(&format!(" OFFSET {}", offset));
2997    }
2998
2999    // What the client actually asked for. Two selections of one aggregate are
3000    // one entry: `sum { id }` beside `totals: sum { views }` reads the same
3001    // object under two names, so the columns are the union of what both named
3002    // and the object is built once.
3003    let mut wants_nodes = false;
3004    let mut wanted: BTreeMap<String, Vec<String>> = BTreeMap::new();
3005    // `count` is different: each occurrence may count something else, so each
3006    // is answered under the name it was asked for.
3007    let mut counts: Vec<(String, String)> = Vec::new();
3008    for selection in ctx.field().selection_set() {
3009        match selection.name() {
3010            "nodes" => wants_nodes = true,
3011            "aggregate" => {
3012                for function in selection.selection_set() {
3013                    if function.name() == "count" {
3014                        counts.push((
3015                            function.alias().unwrap_or("count").to_string(),
3016                            count_expression(
3017                                function,
3018                                (&spec.schema_name, &spec.table_name),
3019                                None,
3020                                spec.names.as_ref(),
3021                            ),
3022                        ));
3023                        continue;
3024                    }
3025                    let columns = wanted.entry(function.name().to_string()).or_default();
3026                    for column in function.selection_set() {
3027                        let name = column.name().to_string();
3028                        if !columns.contains(&name) {
3029                            columns.push(name);
3030                        }
3031                    }
3032                }
3033            }
3034            _ => {}
3035        }
3036    }
3037    wanted.retain(|_, columns| !columns.is_empty());
3038
3039    let mut result = async_graphql::indexmap::IndexMap::new();
3040
3041    if !counts.is_empty() || !wanted.is_empty() {
3042        // `count` is answered whether or not it was asked for: the aggregate
3043        // type's own resolver reads a missing key as zero, and a client that
3044        // asked for nothing else still gets a number rather than a null.
3045        let mut parts = match counts.iter().any(|(key, _)| key == "count") {
3046            true => Vec::new(),
3047            false => vec!["'count', count(*)".to_string()],
3048        };
3049        for (key, expression) in &counts {
3050            parts.push(format!("'{}', {}", key.replace('\'', "''"), expression));
3051        }
3052        for (function, columns) in &wanted {
3053            // The function name comes from the schema, not from the request:
3054            // a selection can only name a field that was generated, so there
3055            // is no path from client text to this identifier.
3056            let sql_function = agg::NUMERIC_AGGREGATES
3057                .iter()
3058                .chain(agg::ORDERED_AGGREGATES.iter())
3059                .find(|(name, _)| name == function)
3060                .map(|(name, _)| *name)
3061                .ok_or_else(|| {
3062                    async_graphql::Error::new(format!("unknown aggregate \"{}\"", function))
3063                })?;
3064            let per_column: Vec<String> = columns
3065                .iter()
3066                .map(|field| {
3067                    let source = spec
3068                        .names
3069                        .column_source(&spec.schema_name, &spec.table_name, field)
3070                        .unwrap_or(field);
3071                    format!(
3072                        "'{}', {}({})",
3073                        field.replace('\'', "''"),
3074                        sql_function,
3075                        postrust_sql::escape_ident(source)
3076                    )
3077                })
3078                .collect();
3079            parts.push(format!(
3080                "'{}', json_build_object({})",
3081                sql_function,
3082                per_column.join(", ")
3083            ));
3084        }
3085
3086        let sql = format!(
3087            "SELECT json_build_object({}) FROM ({}) AS pgrst_agg",
3088            parts.join(", "),
3089            counted
3090        );
3091        let mut conn =
3092            begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3093        let rows = execute_query_on(&mut conn, &sql, &bound_values).await?;
3094        conn.commit().await?;
3095        if let Some(first) = rows.into_iter().next() {
3096            if let Value::Object(map) = json_to_value(first) {
3097                result.insert(async_graphql::Name::new("aggregate"), Value::Object(map));
3098            }
3099        }
3100    }
3101
3102    if wants_nodes {
3103        // `nodes` is the same rows selection the plain root field answers, so
3104        // it gets the same projection: computed fields, which are functions of
3105        // the row rather than part of it, and relationships, which are
3106        // correlated subselects. Reading it as columns alone was why a
3107        // relationship asked for beside a count came back null.
3108        let nodes = ctx
3109            .field()
3110            .selection_set()
3111            .find(|selection| selection.name() == "nodes")
3112            .ok_or_else(|| async_graphql::Error::new("nodes was asked for and then was not"))?;
3113
3114        // The aggregate query has already been sent, so its parameters are
3115        // fixed; anything an embed binds continues that numbering in a vector
3116        // of its own.
3117        let mut node_values = bound_values.clone();
3118        let (projection, row_column_types) = {
3119            let guard = gql_ctx
3120                .schema_cache
3121                .get()
3122                .await
3123                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3124            let cache = guard
3125                .as_ref()
3126                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3127            let qi = postrust_core::api_request::QualifiedIdentifier::new(
3128                &spec.schema_name,
3129                &spec.table_name,
3130            );
3131            let table = cache.get_table(&qi);
3132            let mut param_idx = node_values.len() + 1;
3133            let computed = match table {
3134                Some(table) => computed_projections(
3135                    table,
3136                    nodes,
3137                    "src",
3138                    spec.names.as_ref(),
3139                    cache,
3140                    &mut param_idx,
3141                    &mut node_values,
3142                )?,
3143                None => Vec::new(),
3144            };
3145            let embeds = build_embed_expressions(
3146                &gql_ctx.caller(),
3147                cache,
3148                spec.relationships.as_ref(),
3149                &spec.type_name,
3150                "src",
3151                nodes,
3152                spec.max_rows,
3153                &mut 0,
3154                &mut param_idx,
3155                &mut node_values,
3156                spec.names.as_ref(),
3157            )?;
3158            // A renamed column is renamed here, in the projection over the
3159            // subquery -- not inside it, where `src` has to stay the table's
3160            // own composite for a computed field to be passed the row.
3161            let mut projection = table
3162                .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
3163                .unwrap_or_else(|| "src.*".to_string());
3164            for expression in &computed {
3165                projection.push_str(", ");
3166                projection.push_str(expression);
3167            }
3168            for (field_name, expression) in &embeds {
3169                projection.push_str(", ");
3170                projection.push_str(expression);
3171                projection.push_str(" AS ");
3172                projection.push_str(&postrust_sql::escape_ident(field_name));
3173            }
3174            (
3175                projection,
3176                table
3177                    .map(|table| exposed_column_types(table, spec.names.as_ref()))
3178                    .unwrap_or_default(),
3179            )
3180        };
3181
3182        let sql = format!(
3183            "SELECT {} FROM (SELECT {} FROM ({}) AS src) AS pgrst_nodes",
3184            row_json("pgrst_nodes", &row_column_types),
3185            projection,
3186            inner
3187        );
3188        let mut conn =
3189            begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3190        let rows = execute_query_on(&mut conn, &sql, &node_values).await?;
3191        conn.commit().await?;
3192        result.insert(
3193            async_graphql::Name::new("nodes"),
3194            Value::List(rows.into_iter().map(json_to_value).collect()),
3195        );
3196    }
3197
3198    let _ = &spec.type_name;
3199    Ok(Value::Object(result))
3200}
3201
3202/// Resolve a query field.
3203async fn resolve_query<'a>(
3204    ctx: &ResolverContext<'a>,
3205    spec: &QueryFieldSpec,
3206) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3207    Ok(rows_as_field_value(
3208        query_rows(ctx, spec).await?,
3209        spec.is_by_pk,
3210    ))
3211}
3212
3213/// The rows a query field answers with, as JSON.
3214///
3215/// Split from the field's own resolver because a subscription reads the same
3216/// rows over and over and has to tell one answer from the last: a
3217/// `FieldValue` is neither comparable nor readable, and this is.
3218async fn query_rows(
3219    ctx: &ResolverContext<'_>,
3220    spec: &QueryFieldSpec,
3221) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
3222    let schema_name = spec.schema_name.as_str();
3223    let table_name = spec.table_name.as_str();
3224    let type_name = spec.type_name.as_str();
3225    let is_by_pk = spec.is_by_pk;
3226    let pk_columns = spec.pk_columns.as_slice();
3227    let max_rows = spec.max_rows;
3228    let relationships = spec.relationships.as_ref();
3229
3230    let pool = ctx.data::<PgPool>()?;
3231    let gql_ctx = ctx.data::<GraphQLContext>()?;
3232
3233    debug!("Resolving query for table: {}", table_name);
3234
3235    // Extract pagination arguments
3236    let requested_limit: Option<i64> = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
3237
3238    let offset: Option<i64> = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
3239
3240    // A query that names no limit would otherwise select the whole table, so
3241    // the configured ceiling is applied as the limit in that case, and as an
3242    // upper bound when the query asks for more than it. A by-PK query resolves
3243    // to at most one row.
3244    let limit: Option<i64> = if is_by_pk {
3245        Some(1)
3246    } else {
3247        // The permission's ceiling is one more of the same kind, so it folds
3248        // in the same way: whichever of the three is smallest wins, and a
3249        // request naming none takes the lowest that was named.
3250        let ceiling = match (
3251            max_rows,
3252            permission_limit(
3253                &gql_ctx.caller(),
3254                spec.names.as_ref(),
3255                schema_name,
3256                table_name,
3257            ),
3258        ) {
3259            (Some(configured), Some(granted)) => Some(configured.min(granted)),
3260            (only, None) | (None, only) => only,
3261        };
3262        match (requested_limit, ceiling) {
3263            (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
3264            (Some(requested), None) => Some(requested),
3265            (None, ceiling) => ceiling,
3266        }
3267    };
3268
3269    // Build the WHERE clause.
3270    //
3271    // A by-PK query filters on the table's key columns; each value is bound as
3272    // a parameter and cast to the column's type, since GraphQL scalars and
3273    // PostgreSQL types do not line up (a `uuid` key arrives as a String). A
3274    // list query filters on the `filter` argument, which takes the same shape
3275    // as a mutation's `where`.
3276    let mut where_sql = String::new();
3277    let mut bound_values: Vec<serde_json::Value> = Vec::new();
3278
3279    // Where the rows come from. A function's own arguments are bound before
3280    // anything else, because they sit in the FROM clause and every other
3281    // parameter is numbered after them.
3282    let source = match &spec.call {
3283        // Aliased rather than named. A whole-row reference -- what a computed
3284        // field is passed -- can only be written as a bare name, and
3285        // `"public"."author"` is not one: PostgreSQL reads it as a column of a
3286        // table called `public`. An alias no column can share is a name that
3287        // works in both positions.
3288        None => format!(
3289            "{}.{} AS {}",
3290            postrust_sql::escape_ident(schema_name),
3291            postrust_sql::escape_ident(table_name),
3292            postrust_sql::escape_ident(READ_ROW)
3293        ),
3294        Some(call) => function_source(ctx, call, table_name, &mut bound_values)?,
3295    };
3296
3297    // How a column of the source is referred to: a table by its qualified
3298    // name, a function's rows by the alias above.
3299    let source_ref = match &spec.call {
3300        None => postrust_sql::escape_ident(READ_ROW),
3301        Some(_) => postrust_sql::escape_ident(table_name),
3302    };
3303
3304    if is_by_pk {
3305        if pk_columns.is_empty() {
3306            return Err(async_graphql::Error::new(format!(
3307                "\"{}\" has no primary key, so it cannot be queried by key",
3308                table_name
3309            )));
3310        }
3311
3312        let mut conditions = Vec::with_capacity(pk_columns.len());
3313        for (idx, (col_name, pg_type)) in pk_columns.iter().enumerate() {
3314            let field = spec
3315                .names
3316                .column(schema_name, table_name, col_name)
3317                .unwrap_or(col_name);
3318            let value = ctx.args.try_get(field).map_err(|_| {
3319                async_graphql::Error::new(format!(
3320                    "missing required primary key argument \"{}\"",
3321                    field
3322                ))
3323            })?;
3324
3325            conditions.push(format!(
3326                "{} = ${}::{}",
3327                postrust_sql::escape_ident(col_name),
3328                idx + 1,
3329                pg_type
3330            ));
3331            bound_values.push(accessor_to_json(&value));
3332        }
3333        where_sql = format!(" WHERE {}", conditions.join(" AND "));
3334    }
3335
3336    // What this role may read, beside what the request asked for. Applied to
3337    // both shapes and to neither: a query naming no `where` at all is still a
3338    // read, and a by-key query that skipped the permission would be the one
3339    // way to fetch a row the filter withholds.
3340    {
3341        let requested = match is_by_pk {
3342            true => None,
3343            false => ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v)),
3344        };
3345        let permission = permission_predicate(
3346            &gql_ctx.caller(),
3347            spec.names.as_ref(),
3348            schema_name,
3349            table_name,
3350        )?;
3351
3352        if let Some(predicate) = and_predicates(requested, permission) {
3353            let guard = gql_ctx
3354                .schema_cache
3355                .get()
3356                .await
3357                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3358            let cache = guard
3359                .as_ref()
3360                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3361            let scope = match &spec.call {
3362                None => WhereScope::table(schema_name, table_name, type_name, spec.names.as_ref())
3363                    .under_alias(READ_ROW)
3364                    .with_resolution(cache, relationships)
3365                    .for_caller(gql_ctx.caller()),
3366                // A function's rows are the table's rows, so they are renamed
3367                // the same way; only how they are reached differs.
3368                Some(_) => WhereScope::for_alias(
3369                    schema_name,
3370                    table_name,
3371                    table_name,
3372                    type_name,
3373                    cache,
3374                    relationships,
3375                    spec.names.as_ref(),
3376                )
3377                .for_caller(gql_ctx.caller()),
3378            };
3379            let (filter_sql, filter_values) =
3380                build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3381            if !filter_sql.is_empty() {
3382                // A by-key query already has a `WHERE`, so this joins it
3383                // rather than replacing it.
3384                where_sql = match where_sql.is_empty() {
3385                    true => format!(" {}", filter_sql),
3386                    false => format!(
3387                        "{} AND ({})",
3388                        where_sql,
3389                        filter_sql.trim_start_matches("WHERE ")
3390                    ),
3391                };
3392                bound_values.extend(filter_values);
3393            }
3394        }
3395    }
3396
3397    // A by-key query resolves to one row, so neither ordering nor distinct
3398    // has anything to do.
3399    let (order_sql, distinct_on) = if is_by_pk {
3400        (String::new(), Vec::new())
3401    } else {
3402        (
3403            build_order_by_clause(
3404                ctx,
3405                &gql_ctx.schema_cache,
3406                schema_name,
3407                table_name,
3408                type_name,
3409                relationships,
3410                &source_ref,
3411                spec.names.as_ref(),
3412            )
3413            .await?,
3414            build_distinct_on(
3415                ctx,
3416                &gql_ctx.schema_cache,
3417                schema_name,
3418                table_name,
3419                spec.names.as_ref(),
3420            )
3421            .await?,
3422        )
3423    };
3424
3425    // PostgreSQL keeps the first row of each DISTINCT ON group in the query's
3426    // own order, and picks arbitrarily where the ordering does not begin with
3427    // the distinct columns -- so which row survives would depend on the plan.
3428    // Hasura refuses that query rather than answering it, and so does this:
3429    // prepending the distinct columns instead produced an answer, and a wrong
3430    // one, since `ORDER BY "department", "department" DESC` is decided by its
3431    // first term and sorts ascending.
3432    let (distinct_sql, order_sql) = {
3433        let written = order_sql.strip_prefix(" ORDER BY ");
3434        // Reported against the field's arguments as a whole: the rule is about
3435        // two of them disagreeing, so neither one is where it went wrong.
3436        let (prefix, order) = distinct_on_clause(&distinct_on, written).map_err(|error| {
3437            at_path(
3438                error,
3439                &format!(
3440                    "$.selectionSet.{}.args",
3441                    ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
3442                ),
3443            )
3444        })?;
3445        (
3446            prefix,
3447            match order {
3448                Some(order) => format!(" ORDER BY {}", order),
3449                None => String::new(),
3450            },
3451        )
3452    };
3453
3454    // A computed column is a function of the row rather than part of it, so
3455    // it is not in `*` and is named only when it was asked for.
3456    let (computed, row_column_types) = {
3457        let guard = gql_ctx
3458            .schema_cache
3459            .get()
3460            .await
3461            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3462        match guard.as_ref() {
3463            Some(cache) => {
3464                let qi =
3465                    postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
3466                match cache.get_table(&qi) {
3467                    Some(table) => {
3468                        let mut param_idx = bound_values.len() + 1;
3469                        (
3470                            computed_projections(
3471                                table,
3472                                ctx.field(),
3473                                "src",
3474                                spec.names.as_ref(),
3475                                cache,
3476                                &mut param_idx,
3477                                &mut bound_values,
3478                            )?,
3479                            exposed_column_types(table, spec.names.as_ref()),
3480                        )
3481                    }
3482                    None => (Vec::new(), HashMap::new()),
3483                }
3484            }
3485            None => (Vec::new(), HashMap::new()),
3486        }
3487    };
3488    // ORDER BY, LIMIT and OFFSET belong inside the subquery: applying them to
3489    // the outer `row_to_json` projection would leave the ordering of the rows
3490    // that survive the limit unspecified.
3491    let mut inner = format!(
3492        "SELECT {}* FROM {}{}{}",
3493        distinct_sql, source, where_sql, order_sql
3494    );
3495
3496    if let Some(limit) = limit {
3497        inner.push_str(&format!(" LIMIT {}", limit));
3498    }
3499
3500    if let Some(offset) = offset {
3501        inner.push_str(&format!(" OFFSET {}", offset));
3502    }
3503
3504    // Embed the requested relationships in this same query, as correlated
3505    // subselects in the SELECT list, so the whole selection is one round trip.
3506    let embed_expressions = {
3507        let guard = gql_ctx
3508            .schema_cache
3509            .get()
3510            .await
3511            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3512        match guard.as_ref() {
3513            Some(cache) => {
3514                // Parameters bound inside an embed continue the outer query's
3515                // numbering: sqlx binds by position, so the values only have
3516                // to be pushed in the order their placeholders were handed
3517                // out.
3518                let mut param_idx = bound_values.len() + 1;
3519                build_embed_expressions(
3520                    &gql_ctx.caller(),
3521                    cache,
3522                    relationships,
3523                    type_name,
3524                    "src",
3525                    ctx.field(),
3526                    max_rows,
3527                    &mut 0,
3528                    &mut param_idx,
3529                    &mut bound_values,
3530                    spec.names.as_ref(),
3531                )?
3532            }
3533            None => Vec::new(),
3534        }
3535    };
3536
3537    // A computed column is a function of the row, and the row it is a function
3538    // of is the table's -- not the subquery's. Projecting it inside the
3539    // subquery gave `src` an extra column, which is what made a computed
3540    // relationship beside it fail with `cannot cast type record to author`: a
3541    // subquery alias is only passable as a composite value while its columns
3542    // are exactly the table's. So both live out here, where `src` still is
3543    // one.
3544    let renamed = {
3545        let guard = gql_ctx
3546            .schema_cache
3547            .get()
3548            .await
3549            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3550        guard
3551            .as_ref()
3552            .and_then(|cache| {
3553                cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3554                    schema_name,
3555                    table_name,
3556                ))
3557            })
3558            .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
3559    };
3560    let inner = if embed_expressions.is_empty() && computed.is_empty() && renamed.is_none() {
3561        inner
3562    } else {
3563        let mut projection = renamed.unwrap_or_else(|| "src.*".to_string());
3564        for expression in &computed {
3565            projection.push_str(", ");
3566            projection.push_str(expression);
3567        }
3568        for (field_name, expression) in &embed_expressions {
3569            projection.push_str(", ");
3570            projection.push_str(expression);
3571            projection.push_str(" AS ");
3572            projection.push_str(&postrust_sql::escape_ident(field_name));
3573        }
3574        format!("SELECT {} FROM ({}) AS src", projection, inner)
3575    };
3576
3577    let sql = format!(
3578        "SELECT {} FROM ({}) t",
3579        row_json("t", &row_column_types),
3580        inner
3581    );
3582
3583    // One transaction for the query and any embeds hanging off it, so the role
3584    // applies to all of them and the parent and child rows come from a single
3585    // snapshot.
3586    let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3587
3588    // Execute query - returns Vec<serde_json::Value>
3589    let mut result = execute_query_on(&mut tx, &sql, &bound_values).await?;
3590
3591    // Anything the single-query form did not cover.
3592    if embed_expressions.is_empty() {
3593        let guard = gql_ctx
3594            .schema_cache
3595            .get()
3596            .await
3597            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3598        let cache = guard
3599            .as_ref()
3600            .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3601
3602        let embed_ctx = EmbedContext {
3603            schema_cache: cache,
3604            relationships,
3605            max_rows,
3606        };
3607
3608        embed_relationships(&mut tx, &embed_ctx, type_name, ctx.field(), &mut result).await?;
3609    }
3610
3611    tx.commit().await?;
3612
3613    Ok(result)
3614}
3615
3616/// The rows a field answers with, in the shape its type says.
3617///
3618/// A by-key field is one row or none; every other is a list, empty where
3619/// nothing matched rather than null.
3620fn rows_as_field_value<'a>(rows: Vec<serde_json::Value>, is_by_pk: bool) -> Option<FieldValue<'a>> {
3621    match is_by_pk {
3622        true => rows
3623            .into_iter()
3624            .next()
3625            .map(|v| FieldValue::value(json_to_value(v))),
3626        false => Some(FieldValue::list(
3627            rows.into_iter()
3628                .map(|v| FieldValue::value(json_to_value(v))),
3629        )),
3630    }
3631}
3632
3633/// Resolve a mutation field.
3634#[allow(clippy::too_many_arguments)]
3635async fn resolve_mutation<'a>(
3636    ctx: &ResolverContext<'a>,
3637    schema_name: &str,
3638    table_name: &str,
3639    mutation_type: MutationType,
3640    pk_columns: &[(String, String)],
3641    relationships: &Arc<HashMap<String, Vec<RelationshipField>>>,
3642    type_names: &Arc<HashMap<(String, String), String>>,
3643    names: &Arc<crate::names::NameOverrides>,
3644    max_rows: Option<i64>,
3645) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3646    let pool = ctx.data::<PgPool>()?;
3647    let gql_ctx = ctx.data::<GraphQLContext>()?;
3648
3649    debug!(
3650        "Resolving mutation for table: {} type: {:?}",
3651        table_name, mutation_type
3652    );
3653
3654    // A relationship or a computed field asked for beside the written columns
3655    // is not in `RETURNING`, so the rows are read again through the projection
3656    // an ordinary query uses -- and only when the selection asks for something
3657    // that needs it. A delete cannot do that afterwards and builds it into the
3658    // statement instead, which is why this is settled before the statement is.
3659    let type_name = type_names
3660        .get(&(schema_name.to_string(), table_name.to_string()))
3661        .cloned()
3662        .unwrap_or_else(|| table_name.to_string());
3663
3664    let by_key = matches!(
3665        mutation_type,
3666        MutationType::InsertOne | MutationType::UpdateByPk | MutationType::DeleteByPk
3667    );
3668    let returning = if by_key {
3669        Some(ctx.field())
3670    } else {
3671        ctx.field()
3672            .selection_set()
3673            .find(|selection| selection.name() == "returning")
3674    };
3675
3676    // Several updates, each with its own filter, applied in the order given.
3677    // They share the operation's transaction like any other write, which is
3678    // the whole difference from sending them one at a time: either all of them
3679    // happened or none did.
3680    if mutation_type == MutationType::UpdateMany {
3681        let updates = ctx
3682            .args
3683            .try_get("updates")
3684            .ok()
3685            .map(|v| accessor_to_json(&v))
3686            .unwrap_or(serde_json::Value::Null);
3687        let serde_json::Value::Array(entries) = updates else {
3688            return Err(async_graphql::Error::new(
3689                "\"updates\" takes a list of {where, _set, …} objects",
3690            ));
3691        };
3692        let column_types = column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await;
3693        let mut answers: Vec<FieldValue<'_>> = Vec::with_capacity(entries.len());
3694        for entry in entries {
3695            let serde_json::Value::Object(entry) = entry else {
3696                return Err(async_graphql::Error::new(
3697                    "each update is an object of {where, _set, …}",
3698                ));
3699            };
3700            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
3701                .iter()
3702                .filter_map(|name| entry.get(*name).map(|value| (*name, value.clone())))
3703                .filter(|(_, value)| !value.is_null())
3704                .collect();
3705            let rows = execute_update(
3706                pool,
3707                gql_ctx,
3708                schema_name,
3709                table_name,
3710                &type_name,
3711                operators,
3712                column_types.clone(),
3713                entry.get("where").cloned(),
3714                relationships.as_ref(),
3715                names.as_ref(),
3716            )
3717            .await?;
3718            let affected = rows.len();
3719            let rows = match returning {
3720                Some(returning) if !rows.is_empty() => {
3721                    reread_returning(
3722                        pool,
3723                        gql_ctx,
3724                        schema_name,
3725                        table_name,
3726                        &type_name,
3727                        rows,
3728                        returning,
3729                        relationships.as_ref(),
3730                        names.as_ref(),
3731                        max_rows,
3732                    )
3733                    .await?
3734                }
3735                _ => rows,
3736            };
3737            answers.extend(mutation_result(rows, affected, false));
3738        }
3739        return Ok(Some(FieldValue::list(answers)));
3740    }
3741
3742    let (result, affected) = match mutation_type {
3743        MutationType::Insert | MutationType::InsertOne => {
3744            let objects = ctx
3745                .args
3746                .try_get("objects")
3747                .ok()
3748                .map(|v| accessor_to_json(&v))
3749                .or_else(|| {
3750                    // `insert_x_one(object: {...})` is one row spelled without
3751                    // the list.
3752                    ctx.args
3753                        .try_get("object")
3754                        .ok()
3755                        .map(|v| serde_json::Value::Array(vec![accessor_to_json(&v)]))
3756                })
3757                .unwrap_or_else(|| serde_json::Value::Array(vec![]));
3758
3759            let guard = gql_ctx
3760                .schema_cache
3761                .get()
3762                .await
3763                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3764            let cache = guard
3765                .as_ref()
3766                .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3767            // Where this write is, in the document the client sent. The
3768            // field as it was written -- an alias if there was one, since that
3769            // is what names this selection.
3770            let here = format!(
3771                "$.selectionSet.{}",
3772                ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
3773            );
3774            // `insert_x(objects: [...])` against `insert_x_one(object: {...})`:
3775            // the same write under two spellings, and the path says which.
3776            let named = match mutation_type {
3777                MutationType::InsertOne => "object",
3778                _ => "objects",
3779            };
3780            let context = InsertContext {
3781                on_conflict: ctx
3782                    .args
3783                    .try_get("on_conflict")
3784                    .ok()
3785                    .map(|v| accessor_to_json(&v))
3786                    .filter(|v| !v.is_null()),
3787                cache,
3788                relationships: relationships.as_ref(),
3789                type_names: type_names.as_ref(),
3790                names: names.as_ref(),
3791                caller: gql_ctx.caller(),
3792                // Filled in per row by `execute_insert`, which is where the
3793                // index is known.
3794                row: String::new(),
3795                objects: format!("{}.args.{}", here, named),
3796                conflict: format!("{}.args.on_conflict", here),
3797            };
3798
3799            execute_insert(
3800                pool,
3801                gql_ctx,
3802                schema_name,
3803                table_name,
3804                objects,
3805                &context,
3806                mutation_type == MutationType::InsertOne,
3807            )
3808            .await?
3809        }
3810        // `UpdateMany` answered above: it is a list of responses rather than
3811        // one, so it does not share this shape.
3812        MutationType::UpdateMany | MutationType::Update | MutationType::UpdateByPk => {
3813            // `_set` replaces, the others read the column they write. A client
3814            // may send more than one, so all of them are collected rather than
3815            // the first that happens to be present.
3816            let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
3817                .iter()
3818                .filter_map(|name| {
3819                    ctx.args
3820                        .try_get(name)
3821                        .ok()
3822                        .map(|v| (*name, accessor_to_json(&v)))
3823                })
3824                .filter(|(_, value)| !value.is_null())
3825                .collect();
3826
3827            let where_clause = if mutation_type == MutationType::UpdateByPk {
3828                Some(pk_where_from_args(
3829                    ctx,
3830                    schema_name,
3831                    table_name,
3832                    pk_columns,
3833                    names.as_ref(),
3834                )?)
3835            } else {
3836                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
3837            };
3838            // Which rows this role may change, beside which rows the request
3839            // asked to change. A by-key update is narrowed too: naming the key
3840            // of a row the permission withholds must not be a way to reach it.
3841            let where_clause = and_predicates(
3842                where_clause,
3843                permission_filter(
3844                    &gql_ctx.caller(),
3845                    names.as_ref(),
3846                    schema_name,
3847                    table_name,
3848                    crate::role::Verb::Update,
3849                )?,
3850            );
3851
3852            execute_update(
3853                pool,
3854                gql_ctx,
3855                schema_name,
3856                table_name,
3857                &type_name,
3858                operators,
3859                column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await,
3860                where_clause,
3861                relationships.as_ref(),
3862                names.as_ref(),
3863            )
3864            .await
3865            .map(|rows| {
3866                let count = rows.len();
3867                (rows, count)
3868            })?
3869        }
3870        MutationType::Delete | MutationType::DeleteByPk => {
3871            let where_clause = if mutation_type == MutationType::DeleteByPk {
3872                Some(pk_where_from_args(
3873                    ctx,
3874                    schema_name,
3875                    table_name,
3876                    pk_columns,
3877                    names.as_ref(),
3878                )?)
3879            } else {
3880                ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
3881            };
3882            let where_clause = and_predicates(
3883                where_clause,
3884                permission_filter(
3885                    &gql_ctx.caller(),
3886                    names.as_ref(),
3887                    schema_name,
3888                    table_name,
3889                    crate::role::Verb::Delete,
3890                )?,
3891            );
3892
3893            execute_delete(
3894                pool,
3895                gql_ctx,
3896                schema_name,
3897                table_name,
3898                &type_name,
3899                where_clause,
3900                returning,
3901                relationships.as_ref(),
3902                names.as_ref(),
3903                max_rows,
3904            )
3905            .await
3906            .map(|rows| {
3907                let count = rows.len();
3908                (rows, count)
3909            })?
3910        }
3911    };
3912
3913    let is_delete = matches!(
3914        mutation_type,
3915        MutationType::Delete | MutationType::DeleteByPk
3916    );
3917    let result = match returning {
3918        Some(returning) if !result.is_empty() && !is_delete => {
3919            reread_returning(
3920                pool,
3921                gql_ctx,
3922                schema_name,
3923                table_name,
3924                &type_name,
3925                result,
3926                returning,
3927                relationships.as_ref(),
3928                names.as_ref(),
3929                max_rows,
3930            )
3931            .await?
3932        }
3933        _ => result,
3934    };
3935
3936    Ok(mutation_result(result, affected, by_key))
3937}
3938
3939/// The transaction this operation's writes share, opening it if this is the
3940/// first one.
3941///
3942/// Held for the length of the write, and settled by whoever answers the
3943/// request -- see [`crate::context::SharedWrite`]. Mutation root fields are
3944/// resolved one after another, so the lock is never contended; it is what
3945/// makes the transaction reachable from a resolver that owns nothing.
3946async fn write_tx<'a>(
3947    gql_ctx: &'a GraphQLContext,
3948    pool: &PgPool,
3949) -> Result<
3950    tokio::sync::MutexGuard<'a, Option<sqlx::Transaction<'static, sqlx::Postgres>>>,
3951    async_graphql::Error,
3952> {
3953    let mut guard = gql_ctx.write.lock().await;
3954    if guard.is_none() {
3955        *guard = Some(begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?);
3956    }
3957    Ok(guard)
3958}
3959
3960/// Begin a transaction with the request's role and session variables applied.
3961///
3962/// The settings go in before the role does. A role with fewer privileges may
3963/// not be allowed to call `set_config` at all, and a policy that reads a
3964/// setting the caller could then change is not a policy.
3965async fn begin_with_session(
3966    pool: &PgPool,
3967    role: &str,
3968    settings: &[(String, String)],
3969) -> Result<sqlx::Transaction<'static, sqlx::Postgres>, async_graphql::Error> {
3970    let mut tx = pool.begin().await?;
3971
3972    // One statement for all of them, which is what the REST path does. A loop
3973    // here costs a round trip per session variable, and there is always at
3974    // least one: `hasura.session` and `hasura.role` are set on every request,
3975    // so even an anonymous read paid two. An authenticated caller with six
3976    // `x-hasura-*` variables paid eight.
3977    //
3978    // The values stay bound rather than interpolated. `set_config` takes both
3979    // as arguments, where `SET LOCAL` would need the value written into the
3980    // statement text.
3981    if !settings.is_empty() {
3982        let calls = (0..settings.len())
3983            .map(|i| format!("set_config(${}, ${}, true)", i * 2 + 1, i * 2 + 2))
3984            .collect::<Vec<_>>()
3985            .join(", ");
3986        let sql = format!("SELECT {calls}");
3987        let mut query = sqlx::query_scalar::<_, String>(&sql);
3988        for (name, value) in settings {
3989            query = query.bind(name).bind(value);
3990        }
3991        query.fetch_optional(&mut *tx).await?;
3992    }
3993
3994    sqlx::query(&format!(
3995        "SET LOCAL ROLE {}",
3996        postrust_sql::escape_ident(role)
3997    ))
3998    .execute(&mut *tx)
3999    .await?;
4000    Ok(tx)
4001}
4002
4003/// Render a row as JSON, with any shape column rendered as GeoJSON.
4004///
4005/// PostgreSQL's text form for a geometry is WKB hex --
4006/// `0103000020E6100000…` -- which is what `row_to_json` puts in the response
4007/// and is not what any client can read. Hasura answers GeoJSON, and a client
4008/// migrating from it parses GeoJSON.
4009///
4010/// The shape columns are merged over the row rather than replacing the
4011/// projection, so a table with no shape column produces exactly the SQL it did
4012/// before and one with three needs no list of the others.
4013fn row_json(expr: &str, column_types: &HashMap<String, String>) -> String {
4014    let mut shapes: Vec<(&String, &String)> = column_types
4015        .iter()
4016        .filter(|(_, pg_type)| matches!(pg_type.as_str(), "geometry" | "geography"))
4017        .collect();
4018    if shapes.is_empty() {
4019        return format!("row_to_json({})", expr);
4020    }
4021    shapes.sort();
4022
4023    let overrides: Vec<String> = shapes
4024        .iter()
4025        .map(|(name, _)| {
4026            format!(
4027                // Option 4 asks PostGIS for the long-form CRS member --
4028                // `urn:ogc:def:crs:EPSG::4326` -- which Hasura includes and
4029                // which a client round-tripping a shape needs in order to
4030                // write it back where it came from. PostGIS emits it only
4031                // where the geometry has an SRID to report.
4032                "'{}', ST_AsGeoJSON({}.{}, 9, 4)::jsonb",
4033                name.replace('\'', "''"),
4034                expr,
4035                postrust_sql::escape_ident(name)
4036            )
4037        })
4038        .collect();
4039    format!(
4040        "(to_jsonb({}) || jsonb_build_object({}))",
4041        expr,
4042        overrides.join(", ")
4043    )
4044}
4045
4046/// Execute a SQL query and return results as serde_json::Value.
4047/// We keep data as serde_json::Value so field resolvers can use try_downcast_ref.
4048async fn execute_query_on(
4049    conn: &mut sqlx::PgConnection,
4050    sql: &str,
4051    params: &[serde_json::Value],
4052) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4053    use sqlx::Row;
4054
4055    trace!("Executing SQL: {}", sql);
4056
4057    // Execute query
4058    let mut query = sqlx::query(sql);
4059    for param in params {
4060        query = bind_json_value(query, param);
4061    }
4062    let rows = query.fetch_all(&mut *conn).await.map_err(database_error)?;
4063
4064    // Return raw JSON values - don't convert to async_graphql::Value
4065    // This allows field resolvers to use try_downcast_ref::<serde_json::Value>()
4066    let results: Vec<serde_json::Value> = rows
4067        .into_iter()
4068        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
4069        .collect();
4070
4071    Ok(results)
4072}
4073
4074/// Refuse a GeoJSON document PostGIS would accept and should not.
4075///
4076/// `ST_GeomFromGeoJSON` will build a "Polygon" from three points that do not
4077/// meet, and a "LineString" from one point -- neither of which is a shape, and
4078/// both of which are then stored and read back as though they were. Hasura
4079/// checks the document before it reaches the database, refuses with
4080/// `parse-failed`, and says which rule was broken; the messages here are its
4081/// messages, because a client that reports them to a user is reporting text it
4082/// already ships.
4083///
4084/// A value that is not an object is left alone: a string in a geometry column
4085/// is WKT or WKB hex, which is a different thing entirely.
4086fn check_geojson(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4087    fn refuse<T>(message: &str) -> Result<T, async_graphql::Error> {
4088        Err(coded_error("parse-failed", message))
4089    }
4090
4091    fn position(
4092        value: &serde_json::Value,
4093    ) -> Result<&Vec<serde_json::Value>, async_graphql::Error> {
4094        match value.as_array() {
4095            Some(items) if items.len() >= 2 && items.iter().all(|i| i.is_number()) => Ok(items),
4096            _ => refuse("A Position needs at least 2 elements"),
4097        }
4098    }
4099
4100    fn positions(
4101        value: Option<&serde_json::Value>,
4102        least: usize,
4103        what: &str,
4104    ) -> Result<Vec<Vec<serde_json::Value>>, async_graphql::Error> {
4105        let items = match value.and_then(|v| v.as_array()) {
4106            Some(items) => items,
4107            None => return refuse(&format!("A {} needs at least {} Positions", what, least)),
4108        };
4109        if items.len() < least {
4110            return refuse(&format!("A {} needs at least {} Positions", what, least));
4111        }
4112        items.iter().map(|item| position(item).cloned()).collect()
4113    }
4114
4115    fn ring(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4116        let points = positions(Some(value), 4, "LinearRing")?;
4117        match (points.first(), points.last()) {
4118            (Some(first), Some(last)) if first == last => Ok(()),
4119            _ => refuse("the first and last locations have to be equal for a LinearRing"),
4120        }
4121    }
4122
4123    fn polygon(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4124        let Some(rings) = value.as_array() else {
4125            return refuse("A LinearRing needs at least 4 Positions");
4126        };
4127        rings.iter().try_for_each(ring)
4128    }
4129
4130    let serde_json::Value::Object(map) = value else {
4131        return Ok(());
4132    };
4133    let Some(kind) = map.get("type").and_then(|v| v.as_str()) else {
4134        return Ok(());
4135    };
4136    let coordinates = map.get("coordinates");
4137    match kind {
4138        "Point" => {
4139            position(coordinates.unwrap_or(&serde_json::Value::Null))?;
4140        }
4141        "MultiPoint" => {
4142            positions(coordinates, 0, "MultiPoint")?;
4143        }
4144        "LineString" => {
4145            positions(coordinates, 2, "LineString")?;
4146        }
4147        "MultiLineString" => {
4148            for line in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4149                positions(Some(line), 2, "LineString")?;
4150            }
4151        }
4152        "Polygon" => polygon(coordinates.unwrap_or(&serde_json::Value::Null))?,
4153        "MultiPolygon" => {
4154            for one in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4155                polygon(one)?;
4156            }
4157        }
4158        "GeometryCollection" => {
4159            for geometry in map
4160                .get("geometries")
4161                .and_then(|v| v.as_array())
4162                .into_iter()
4163                .flatten()
4164            {
4165                check_geojson(geometry)?;
4166            }
4167        }
4168        other => return refuse(&format!("unexpected geometry type: {}", other)),
4169    }
4170    Ok(())
4171}
4172
4173/// The call that produces a computed field's value.
4174///
4175/// Two spellings of the same row, because the two calls need different ones.
4176/// A positional call takes `alias.*`, which is what a qualified table can be
4177/// written as; a named call takes the row as one value, which only a bare
4178/// alias is. Named notation is needed exactly where a session argument is,
4179/// since the row is then not the only parameter and may not be the first.
4180fn computed_call(
4181    definition: &postrust_core::schema_cache::ComputedColumn,
4182    row: &str,
4183    row_by_name: &str,
4184) -> String {
4185    let function = format!(
4186        "{}.{}",
4187        postrust_sql::escape_ident(&definition.function.schema),
4188        postrust_sql::escape_ident(&definition.function.name)
4189    );
4190    match (&definition.session_argument, &definition.row_argument) {
4191        (Some(session), Some(row_argument)) => format!(
4192            "{}({} => {}, {} => {})",
4193            function,
4194            postrust_sql::escape_ident(row_argument),
4195            row_by_name,
4196            postrust_sql::escape_ident(session),
4197            SESSION_ARGUMENT
4198        ),
4199        _ => format!("{}({})", function, row),
4200    }
4201}
4202
4203/// What a `hasura_session` argument is given.
4204///
4205/// The session document, read from the setting `begin_with_session` writes.
4206/// `current_setting`'s second argument makes a missing setting null rather than
4207/// an error, which is the case of a request with no session at all.
4208const SESSION_ARGUMENT: &str = "coalesce(current_setting('hasura.session', true), '{}')::json";
4209
4210/// Everything an update may be told to do, in the order the schema declares
4211/// them. Read by both spellings: one update, and one of many.
4212const UPDATE_OPERATORS: [&str; 7] = [
4213    "_set",
4214    "_inc",
4215    "_append",
4216    "_prepend",
4217    "_delete_key",
4218    "_delete_elem",
4219    "_delete_at_path",
4220];
4221
4222/// What a read table is called inside its own query.
4223///
4224/// See [`WRITTEN_ROW`]: the row a query passes to a computed field has to be
4225/// named, and a table's qualified name is not a name a row can be written
4226/// under.
4227const READ_ROW: &str = "pgrst_src";
4228
4229/// What a written table is called inside its own statement.
4230///
4231/// See [`WhereScope::under_alias`]: the row a statement returns has to be
4232/// named, and a table's own name is read as a column first.
4233const WRITTEN_ROW: &str = "pgrst_row";
4234
4235/// The cast a written value needs to reach a column of this type.
4236///
4237/// A bound parameter arrives as text. PostgreSQL will coerce it to a numeric
4238/// or a date on assignment, but not to `jsonb`, an array, or a user-defined
4239/// type -- `column "details" is of type jsonb but expression is of type text`
4240/// is what an uncast insert answers. Naming the column's own type covers all
4241/// of them and changes nothing where the coercion would have worked anyway.
4242fn write_cast(column_types: &HashMap<String, String>, column: &str) -> String {
4243    match column_types.get(column) {
4244        Some(pg_type) => format!("::{}", pg_type),
4245        None => String::new(),
4246    }
4247}
4248
4249/// The value one column is written with, as it goes over the wire.
4250///
4251/// A `json` column holds a JSON value, and `"[]"` is one -- a string whose
4252/// characters happen to be brackets. Binding the bare text and casting stored
4253/// an empty array instead, so a client that wrote a string read back a list.
4254/// The document a column is given is the document it keeps.
4255fn write_operand(
4256    column_types: &HashMap<String, String>,
4257    column: &str,
4258    value: &serde_json::Value,
4259) -> serde_json::Value {
4260    match column_types.get(column).map(String::as_str) {
4261        Some("json") | Some("jsonb") => match value {
4262            serde_json::Value::String(_)
4263            | serde_json::Value::Number(_)
4264            | serde_json::Value::Bool(_) => serde_json::Value::String(value.to_string()),
4265            other => other.clone(),
4266        },
4267        // An array column takes an array literal. `["a","b"]` is JSON, and
4268        // PostgreSQL reads a text parameter destined for a `text[]` as
4269        // `{a,b}` -- `malformed array literal` is what it says about the
4270        // other spelling.
4271        Some(pg_type) if is_array_type(pg_type) => match value {
4272            serde_json::Value::Array(_) => serde_json::Value::String(array_literal(value)),
4273            other => other.clone(),
4274        },
4275        _ => value.clone(),
4276    }
4277}
4278
4279/// Whether a PostgreSQL type name is an array.
4280///
4281/// Both spellings appear in the catalogue depending on how it was asked:
4282/// `text[]` from `format_type`, `_text` from `typname`.
4283fn is_array_type(pg_type: &str) -> bool {
4284    pg_type.ends_with("[]") || pg_type.starts_with('_')
4285}
4286
4287/// A JSON array as PostgreSQL writes one.
4288///
4289/// `["a","b"]` becomes `{"a","b"}`. Every element is quoted, so a value
4290/// containing a comma, a brace or a backslash survives; a null is the unquoted
4291/// `NULL`, which is the only way to write one.
4292fn array_literal(value: &serde_json::Value) -> String {
4293    fn element(value: &serde_json::Value) -> String {
4294        match value {
4295            serde_json::Value::Null => "NULL".to_string(),
4296            serde_json::Value::Array(_) => array_literal(value),
4297            serde_json::Value::String(text) => {
4298                format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
4299            }
4300            other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
4301        }
4302    }
4303    let items = match value {
4304        serde_json::Value::Array(items) => items,
4305        other => return element(other),
4306    };
4307    format!(
4308        "{{{}}}",
4309        items.iter().map(element).collect::<Vec<_>>().join(",")
4310    )
4311}
4312
4313/// The expression that writes one bound value into one column.
4314///
4315/// A cast is enough for almost everything. A shape is the exception: a client
4316/// sends GeoJSON, which is what Hasura accepts, and `'{"type":"Point",…}'` is
4317/// not something PostgreSQL will cast to a geometry -- it has a function for
4318/// reading that, and the function is the only way in.
4319///
4320/// Only when the value actually is an object. A string in a geometry column is
4321/// WKT or WKB hex, which the cast does read, and passing that to
4322/// `ST_GeomFromGeoJSON` would refuse a perfectly good value.
4323fn write_expression(
4324    column_types: &HashMap<String, String>,
4325    column: &str,
4326    value: &serde_json::Value,
4327    placeholder: &str,
4328) -> String {
4329    let pg_type = column_types.get(column).map(String::as_str);
4330    if value.is_object() && matches!(pg_type, Some("geometry") | Some("geography")) {
4331        return match pg_type {
4332            Some("geography") => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
4333            _ => format!("ST_GeomFromGeoJSON({})", placeholder),
4334        };
4335    }
4336    // An amount written as a number is reached through one: PostgreSQL has no
4337    // cast from `double precision` to `money`, which is what a bound JSON
4338    // number arrives as, and `numeric` is the type it does take one from. One
4339    // written as text -- `"$12,344.57"` -- is read by `money` directly, and
4340    // going through `numeric` would refuse the currency symbol.
4341    if pg_type == Some("money") && value.is_number() {
4342        return format!("{}::numeric::money", placeholder);
4343    }
4344    format!("{}{}", placeholder, write_cast(column_types, column))
4345}
4346
4347/// Every column of a table with the type it is declared as.
4348async fn column_types_of(
4349    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
4350    schema_name: &str,
4351    table_name: &str,
4352) -> HashMap<String, String> {
4353    let Ok(guard) = schema_cache.get().await else {
4354        return HashMap::new();
4355    };
4356    let Some(cache) = guard.as_ref() else {
4357        return HashMap::new();
4358    };
4359    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
4360    match cache.get_table(&qi) {
4361        Some(table) => table
4362            .columns
4363            .values()
4364            .map(|c| (c.name.clone(), c.nominal_type.clone()))
4365            .collect(),
4366        None => HashMap::new(),
4367    }
4368}
4369
4370/// Insert one row, and any rows nested inside it.
4371///
4372/// Hasura writes a parent and its children in one mutation:
4373///
4374/// ```graphql
4375/// insert_article(objects: [{title: "x", author: {data: {name: "y"}}}])
4376/// ```
4377///
4378/// Which row goes first follows from which side holds the key. A relationship
4379/// to one row is reached through a column of *this* table, so the related row
4380/// is inserted first and its key is what this row's column is set to. A
4381/// relationship to many rows is reached through a column of *theirs*, so this
4382/// row goes first and each child's column is set from it.
4383///
4384/// Everything runs in the transaction the caller opened, so a child that
4385/// cannot be written takes the parent with it -- which is what writing them in
4386/// one mutation means.
4387fn insert_row<'life>(
4388    conn: &'life mut sqlx::PgConnection,
4389    schema_name: &'life str,
4390    table_name: &'life str,
4391    object: serde_json::Map<String, serde_json::Value>,
4392    context: &'life InsertContext<'life>,
4393    written_count: &'life mut usize,
4394) -> std::pin::Pin<
4395    Box<
4396        dyn std::future::Future<Output = Result<serde_json::Value, async_graphql::Error>>
4397            + Send
4398            + 'life,
4399    >,
4400> {
4401    Box::pin(async move {
4402        use sqlx::Row;
4403
4404        let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
4405        let table = context.cache.get_table(&qi).ok_or_else(|| {
4406            async_graphql::Error::new(format!("unknown table \"{}\"", table_name))
4407        })?;
4408        let column_types: HashMap<String, String> = table
4409            .columns
4410            .values()
4411            .map(|c| (c.name.clone(), c.nominal_type.clone()))
4412            .collect();
4413
4414        // A key that is a relationship rather than a column is a nested write.
4415        let type_name = context
4416            .type_names
4417            .get(&(schema_name.to_string(), table_name.to_string()));
4418        let relationships: &[RelationshipField] = type_name
4419            .and_then(|name| context.relationships.get(name))
4420            .map(|r| r.as_slice())
4421            .unwrap_or(&[]);
4422
4423        let mut columns = serde_json::Map::new();
4424        type Nested<'r> = (
4425            &'r RelationshipField,
4426            serde_json::Value,
4427            Option<serde_json::Value>,
4428        );
4429        let mut to_one: Vec<Nested> = Vec::new();
4430        let mut to_many: Vec<Nested> = Vec::new();
4431
4432        for (key, value) in object {
4433            match relationships.iter().find(|r| r.name == key) {
4434                None => {
4435                    // A written value is keyed by the field it was sent under;
4436                    // the statement is written in the table's own columns.
4437                    columns.insert(
4438                        table_column_for(context.names, table, &key).to_string(),
4439                        value,
4440                    );
4441                }
4442                Some(_) if value.is_null() => {
4443                    // `author: null` is a row with no author, not a row whose
4444                    // author is a null object. A client writing one insert for
4445                    // both cases sends the relationship either way.
4446                    continue;
4447                }
4448                Some(relationship) => {
4449                    // `{data: {...}}` for one row, `{data: [{...}]}` for many,
4450                    // and an `on_conflict` beside either -- a nested row is
4451                    // upserted the same way a top-level one is.
4452                    let (data, conflict) = match &value {
4453                        serde_json::Value::Object(map) => (
4454                            map.get("data").cloned().unwrap_or(value.clone()),
4455                            map.get("on_conflict").cloned().filter(|v| !v.is_null()),
4456                        ),
4457                        other => (other.clone(), None),
4458                    };
4459                    // Which row goes first follows from which side holds the
4460                    // key, not from how many rows there are. A one-to-one
4461                    // whose child key *is* the parent's -- `author_detail.id`
4462                    // referencing `author.id` -- is one row either way, and
4463                    // the parent still has to be written before there is a key
4464                    // to give it.
4465                    if child_holds_the_key(&relationship.relationship) {
4466                        to_many.push((relationship, data, conflict));
4467                    } else {
4468                        to_one.push((relationship, data, conflict));
4469                    }
4470                }
4471            }
4472        }
4473
4474        // A shape is checked before it is written, since PostGIS would build
4475        // one from a document that is not a shape.
4476        for (column, value) in &columns {
4477            if matches!(
4478                column_types.get(column).map(String::as_str),
4479                Some("geometry") | Some("geography")
4480            ) {
4481                // Under the name the client wrote it under, which is the
4482                // exposed one rather than the column's where they differ.
4483                check_geojson(value).map_err(|error| {
4484                    let field = context
4485                        .names
4486                        .column(schema_name, table_name, column)
4487                        .unwrap_or(column);
4488                    at_path(error, &format!("{}.{}", context.row, field))
4489                })?;
4490            }
4491        }
4492
4493        // The rows this one points at, first: this row's own column carries
4494        // their key.
4495        for (relationship, data, conflict) in to_one {
4496            let plan =
4497                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
4498                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4499            let serde_json::Value::Object(child) = data else {
4500                return Err(async_graphql::Error::new(format!(
4501                    "\"{}\" takes an object to insert",
4502                    relationship.name
4503                )));
4504            };
4505            // Writing the related row is what fills this row's key column in,
4506            // so a value already written there is a second answer to the same
4507            // question and one of them would be silently discarded.
4508            for (local, _) in &plan.columns {
4509                if columns.get(local).is_some_and(|v| !v.is_null()) {
4510                    return Err(at_path(
4511                        validation_error(format!(
4512                            "cannot insert object relationship \"{}\" as \"{}\" column \
4513                             values are already determined",
4514                            relationship.name, local
4515                        )),
4516                        &format!("{}.{}", context.row, relationship.name),
4517                    ));
4518                }
4519            }
4520            // A to-one row is written under the relationship's own name:
4521            // `...objects[0].author.data`, and its `on_conflict` beside it.
4522            let under = format!("{}.{}", context.row, relationship.name);
4523            let nested = InsertContext {
4524                on_conflict: conflict,
4525                cache: context.cache,
4526                relationships: context.relationships,
4527                type_names: context.type_names,
4528                names: context.names,
4529                caller: context.caller,
4530                row: format!("{}.data", under),
4531                objects: format!("{}.data", under),
4532                conflict: format!("{}.on_conflict", under),
4533            };
4534            let written = insert_row(
4535                conn,
4536                &plan.foreign_schema,
4537                &plan.foreign_table,
4538                child,
4539                &nested,
4540                written_count,
4541            )
4542            .await?;
4543            for (local, foreign) in &plan.columns {
4544                if let Some(value) = written.get(foreign) {
4545                    columns.insert(local.clone(), value.clone());
4546                }
4547            }
4548        }
4549
4550        // What the server fills in regardless of what was sent. Applied after
4551        // the nested rows have contributed their keys and before the columns
4552        // are counted, because a preset is a column this statement writes --
4553        // and applied *over* the request, since a preset the client could
4554        // override is not a preset.
4555        for (column, value) in crate::role::presets(
4556            &context.caller,
4557            context.names,
4558            schema_name,
4559            table_name,
4560            crate::role::Verb::Insert,
4561        )
4562        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?
4563        {
4564            columns.insert(column, value);
4565        }
4566
4567        // `ON CONFLICT` says which uniqueness is being resolved against and
4568        // what to do about it. An empty `update_columns` is `DO NOTHING`,
4569        // which is how a client says "leave the row that is already there".
4570        // The columns this statement writes, settled before the conflict
4571        // clause so a predicate on it can number its parameters after theirs.
4572        let column_names: Vec<&str> = columns.keys().map(|k| k.as_str()).collect();
4573        let mut conflict_values: Vec<serde_json::Value> = Vec::new();
4574        let conflict_sql = match &context.on_conflict {
4575            Some(serde_json::Value::Object(spec)) => {
4576                let constraint = spec.get("constraint").and_then(|v| v.as_str());
4577                let Some(constraint) = constraint else {
4578                    return Err(async_graphql::Error::new(
4579                        "on_conflict needs a constraint to resolve against",
4580                    ));
4581                };
4582                if !table
4583                    .unique_constraints
4584                    .iter()
4585                    .any(|(name, _)| name == constraint)
4586                {
4587                    return Err(async_graphql::Error::new(format!(
4588                        "\"{}\" is not a unique constraint of \"{}\"",
4589                        constraint, table_name
4590                    )));
4591                }
4592                let updates: Vec<&str> = spec
4593                    .get("update_columns")
4594                    .and_then(|v| v.as_array())
4595                    .map(|items| items.iter().filter_map(|i| i.as_str()).collect())
4596                    .unwrap_or_default();
4597                // The member that stands for the columns this role has none
4598                // of. It is in the enum so that `on_conflict` exists at all;
4599                // naming it is the one thing it cannot be used for.
4600                if updates.contains(&PLACEHOLDER_COLUMN) {
4601                    return Err(at_path(
4602                        coded_error("validation-failed", "erroneous column name"),
4603                        &context.conflict,
4604                    ));
4605                }
4606
4607                if updates.is_empty() {
4608                    format!(
4609                        " ON CONFLICT ON CONSTRAINT {} DO NOTHING",
4610                        postrust_sql::escape_ident(constraint)
4611                    )
4612                } else {
4613                    // An `ON CONFLICT DO UPDATE` is an update, so the update
4614                    // permission's presets are written here too -- and a
4615                    // column a preset writes does not also take its value from
4616                    // `EXCLUDED`, which is what "a preset overrides the
4617                    // request" means on this side.
4618                    let preset = crate::role::presets(
4619                        &context.caller,
4620                        context.names,
4621                        schema_name,
4622                        table_name,
4623                        crate::role::Verb::Update,
4624                    )
4625                    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
4626
4627                    // Numbered after the row's own values and before the
4628                    // predicate below, because `SET` is written before `WHERE`
4629                    // and the parameters are bound in the order they appear.
4630                    let mut param_idx = column_names.len() + 1;
4631                    let mut assignments: Vec<String> =
4632                        Vec::with_capacity(preset.len() + updates.len());
4633                    for (column, value) in &preset {
4634                        assignments.push(format!(
4635                            "{} = {}",
4636                            postrust_sql::escape_ident(column),
4637                            write_expression(
4638                                &column_types,
4639                                column,
4640                                value,
4641                                &format!("${}", param_idx)
4642                            )
4643                        ));
4644                        conflict_values.push(write_operand(&column_types, column, value));
4645                        param_idx += 1;
4646                    }
4647                    for field in &updates {
4648                        let column = table_column_for(context.names, table, field);
4649                        if preset.iter().any(|(written, _)| written == column) {
4650                            continue;
4651                        }
4652                        assignments.push(format!(
4653                            "{} = EXCLUDED.{}",
4654                            postrust_sql::escape_ident(column),
4655                            postrust_sql::escape_ident(column)
4656                        ));
4657                    }
4658                    // `where` on an upsert decides whether the row that is
4659                    // already there is overwritten -- "only if what I am
4660                    // writing is newer". It reads the existing row, which is
4661                    // what the statement's alias names.
4662                    //
4663                    // The update permission's own filter is ANDed into it: an
4664                    // upsert that overwrites a row is an update, and a role
4665                    // that may not update a row may not reach it by inserting
4666                    // over it. Compiling that filter is also where a session
4667                    // variable it names and the caller does not carry is
4668                    // noticed -- which is the answer Hasura gives before it
4669                    // writes anything.
4670                    let granted = permission_filter(
4671                        &context.caller,
4672                        context.names,
4673                        schema_name,
4674                        table_name,
4675                        crate::role::Verb::Update,
4676                    )?;
4677                    let requested = spec.get("where").filter(|f| !f.is_null()).cloned();
4678                    let condition = match and_predicates(requested, granted) {
4679                        Some(filter) => {
4680                            let filter = &filter;
4681                            let type_name = context
4682                                .type_names
4683                                .get(&(schema_name.to_string(), table_name.to_string()))
4684                                .map(String::as_str)
4685                                .unwrap_or(table_name);
4686                            let scope = WhereScope::table(
4687                                schema_name,
4688                                table_name,
4689                                type_name,
4690                                context.names,
4691                            )
4692                            .under_alias(WRITTEN_ROW)
4693                            .with_resolution(context.cache, context.relationships);
4694                            let mut alias_counter = 0usize;
4695                            build_condition(
4696                                filter,
4697                                &scope,
4698                                &mut param_idx,
4699                                &mut conflict_values,
4700                                &mut alias_counter,
4701                            )?
4702                            .map(|sql| format!(" WHERE {}", sql))
4703                            .unwrap_or_default()
4704                        }
4705                        None => String::new(),
4706                    };
4707                    format!(
4708                        " ON CONFLICT ON CONSTRAINT {} DO UPDATE SET {}{}",
4709                        postrust_sql::escape_ident(constraint),
4710                        assignments.join(", "),
4711                        condition
4712                    )
4713                }
4714            }
4715            _ => String::new(),
4716        };
4717
4718        // The row this statement writes, as `RETURNING` names it. Not the
4719        // table's own name: qualifying it reads as a column of a table called
4720        // `public`, which is what `missing FROM-clause entry for table
4721        // "public"` means, and leaving it bare reads as a column of the table
4722        // where one shares the name.
4723        let qualified_table = postrust_sql::escape_ident(WRITTEN_ROW);
4724
4725        // What the written row has to satisfy for the write to stand,
4726        // evaluated in `RETURNING` against the row as written. Its parameters
4727        // are numbered after the values and the conflict clause, since
4728        // `RETURNING` is last.
4729        let check_scope = WhereScope::for_alias(
4730            schema_name,
4731            table_name,
4732            WRITTEN_ROW,
4733            context
4734                .type_names
4735                .get(&(schema_name.to_string(), table_name.to_string()))
4736                .map(String::as_str)
4737                .unwrap_or(table_name),
4738            context.cache,
4739            context.relationships,
4740            context.names,
4741        );
4742        let (check_sql, check_values) = {
4743            let check = crate::role::write_check(
4744                &context.caller,
4745                context.names,
4746                schema_name,
4747                table_name,
4748                crate::role::Verb::Insert,
4749            )
4750            .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
4751            match check {
4752                None => (String::new(), Vec::new()),
4753                Some(check) => {
4754                    let mut values = Vec::new();
4755                    let mut param_idx = column_names.len() + conflict_values.len() + 1;
4756                    let mut alias_counter = 0usize;
4757                    match build_condition(
4758                        &check,
4759                        &check_scope,
4760                        &mut param_idx,
4761                        &mut values,
4762                        &mut alias_counter,
4763                    )? {
4764                        None => (String::new(), Vec::new()),
4765                        Some(sql) => (
4766                            format!(
4767                                ", ({}) AS {}",
4768                                sql,
4769                                postrust_sql::escape_ident(CHECK_COLUMN)
4770                            ),
4771                            values,
4772                        ),
4773                    }
4774                }
4775            }
4776        };
4777
4778        let names = column_names;
4779        let written = if names.is_empty() {
4780            // Every column defaulted. `DEFAULT VALUES` is how SQL says that;
4781            // an empty column list is a syntax error.
4782            let sql = format!(
4783                "INSERT INTO {}.{} AS {} DEFAULT VALUES{} RETURNING {}{}",
4784                postrust_sql::escape_ident(schema_name),
4785                postrust_sql::escape_ident(table_name),
4786                qualified_table,
4787                conflict_sql,
4788                row_json(&qualified_table, &column_types),
4789                check_sql
4790            );
4791            let mut query = sqlx::query(&sql);
4792            for value in &conflict_values {
4793                query = bind_json_value(query, value);
4794            }
4795            for value in &check_values {
4796                query = bind_json_value(query, value);
4797            }
4798            query
4799                .fetch_optional(&mut *conn)
4800                .await
4801                // A constraint the write violated is reported against the rows
4802                // the statement was given, which is where Hasura reports it.
4803                .map_err(|error| at_path(database_error(error), &context.objects))?
4804        } else {
4805            let placeholders: Vec<String> = names
4806                .iter()
4807                .enumerate()
4808                .map(|(i, column)| {
4809                    let placeholder = format!("${}", i + 1);
4810                    match columns.get(*column) {
4811                        Some(value) => write_expression(&column_types, column, value, &placeholder),
4812                        None => placeholder,
4813                    }
4814                })
4815                .collect();
4816            let sql = format!(
4817                "INSERT INTO {}.{} AS {} ({}) VALUES ({}){} RETURNING {}{}",
4818                postrust_sql::escape_ident(schema_name),
4819                postrust_sql::escape_ident(table_name),
4820                qualified_table,
4821                names
4822                    .iter()
4823                    .map(|c| postrust_sql::escape_ident(c))
4824                    .collect::<Vec<_>>()
4825                    .join(", "),
4826                placeholders.join(", "),
4827                conflict_sql,
4828                row_json(&qualified_table, &column_types),
4829                check_sql
4830            );
4831            trace!("Executing INSERT SQL: {}", sql);
4832            let mut query = sqlx::query(&sql);
4833            for column in &names {
4834                if let Some(value) = columns.get(*column) {
4835                    query = bind_json_value(query, &write_operand(&column_types, column, value));
4836                }
4837            }
4838            for value in &conflict_values {
4839                query = bind_json_value(query, value);
4840            }
4841            for value in &check_values {
4842                query = bind_json_value(query, value);
4843            }
4844            query
4845                .fetch_optional(&mut *conn)
4846                .await
4847                // A constraint the write violated is reported against the rows
4848                // the statement was given, which is where Hasura reports it.
4849                .map_err(|error| at_path(database_error(error), &context.objects))?
4850        };
4851
4852        // `DO NOTHING` writes nothing and returns nothing, which is the answer
4853        // rather than an error: the row that was already there stays.
4854        let Some(written) = written else {
4855            return Ok(serde_json::Value::Null);
4856        };
4857        refuse_unchecked_rows(std::slice::from_ref(&written), !check_sql.is_empty())
4858            .map_err(|error| at_path(error, &context.objects))?;
4859        let row: serde_json::Value = written.try_get(0).unwrap_or(serde_json::Value::Null);
4860        *written_count += 1;
4861
4862        // Then the rows that point at this one, which need its key.
4863        for (relationship, data, conflict) in to_many {
4864            let plan =
4865                postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
4866                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4867            let children = match data {
4868                serde_json::Value::Array(items) => items,
4869                serde_json::Value::Object(map) => vec![serde_json::Value::Object(map)],
4870                serde_json::Value::Null => Vec::new(),
4871                _ => {
4872                    return Err(async_graphql::Error::new(format!(
4873                        "\"{}\" takes objects to insert",
4874                        relationship.name
4875                    )))
4876                }
4877            };
4878            for (child_index, child) in children.into_iter().enumerate() {
4879                let serde_json::Value::Object(mut child) = child else {
4880                    continue;
4881                };
4882                for (local, foreign) in &plan.columns {
4883                    // The parent's key is what makes the child a child, so a
4884                    // child that writes it too is asking for a different
4885                    // parent than the one it is nested inside.
4886                    if child.get(foreign).is_some_and(|v| !v.is_null()) {
4887                        return Err(at_path(
4888                            validation_error(format!(
4889                                "cannot insert \"{}\" columns as their values are already \
4890                                 being determined by parent insert",
4891                                foreign
4892                            )),
4893                            &format!(
4894                                "{}.{}{}",
4895                                context.row,
4896                                relationship.name,
4897                                match relationship.is_list {
4898                                    true => format!(".data[{}]", child_index),
4899                                    false => ".data".to_string(),
4900                                }
4901                            ),
4902                        ));
4903                    }
4904                    if let Some(value) = row.get(local) {
4905                        child.insert(foreign.clone(), value.clone());
4906                    }
4907                }
4908                // `...objects[0].articles.data[0]`, which is the path
4909                // Hasura answers for a child that writes its parent's key.
4910                // One row written after its parent -- a mapped object
4911                // relationship with `after_parent` -- has no index: there is
4912                // no list for it to be an item of.
4913                let at = match relationship.is_list {
4914                    true => format!(".data[{}]", child_index),
4915                    false => ".data".to_string(),
4916                };
4917                let under = format!("{}.{}", context.row, relationship.name);
4918                let nested = InsertContext {
4919                    on_conflict: conflict.clone(),
4920                    cache: context.cache,
4921                    relationships: context.relationships,
4922                    type_names: context.type_names,
4923                    names: context.names,
4924                    caller: context.caller,
4925                    row: format!("{}{}", under, at),
4926                    objects: format!("{}.data", under),
4927                    conflict: format!("{}.on_conflict", under),
4928                };
4929                insert_row(
4930                    conn,
4931                    &plan.foreign_schema,
4932                    &plan.foreign_table,
4933                    child,
4934                    &nested,
4935                    written_count,
4936                )
4937                .await?;
4938            }
4939        }
4940
4941        Ok(row)
4942    })
4943}
4944
4945/// What a nested insert needs to follow a relationship.
4946///
4947/// Cloned per row and per nested row, which costs three strings: the paths are
4948/// the only part that differs between them, and the rest is references and a
4949/// `Caller`.
4950#[derive(Clone)]
4951struct InsertContext<'a> {
4952    /// What to do when this row conflicts. A nested row carries its own, from
4953    /// the `on_conflict` beside its `data`.
4954    on_conflict: Option<serde_json::Value>,
4955    cache: &'a SchemaCache,
4956    relationships: &'a HashMap<String, Vec<RelationshipField>>,
4957    /// (schema, table) -> the GraphQL type name, which keys the relationship
4958    /// map. A table's type is not always its name: a second schema prefixes
4959    /// it, and a name may have been given.
4960    type_names: &'a HashMap<(String, String), String>,
4961    /// The names columns are exposed under, so a written value keyed by a
4962    /// field reaches the column it names.
4963    names: &'a crate::names::NameOverrides,
4964    /// Who is writing, so that a permission's presets and its check reach
4965    /// every row this insert writes -- including the nested ones, which are
4966    /// rows of other tables under other rules.
4967    caller: crate::role::Caller<'a>,
4968    /// Where this row is in the request, as Hasura writes such a place:
4969    /// `$.selectionSet.insert_author.args.objects[0]`, and one level down
4970    /// `$.selectionSet.insert_author.args.objects[0].articles.data[0]`. A
4971    /// column of it is that with the field's name appended.
4972    row: String,
4973    /// Where the rows this statement writes were named. What a refused `check`
4974    /// is reported against -- the argument, not the row inside it, which is
4975    /// what Hasura answers.
4976    objects: String,
4977    /// Where this statement's `on_conflict` was named.
4978    conflict: String,
4979}
4980
4981/// Re-read written rows so that a mutation's `returning` can carry
4982/// relationships and computed fields.
4983///
4984/// `RETURNING row_to_json(t.*)` gives the table's own columns and nothing
4985/// else, so a relationship asked for beside them had no value to resolve --
4986/// and a non-null list field with no value is an error, which meant a mutation
4987/// that had written its rows correctly answered as though it had failed. The
4988/// write is not in doubt; only the shape of the answer is.
4989///
4990/// So the rows are read again by their primary key, through the same
4991/// projection an ordinary query uses. One extra round trip, and only when the
4992/// selection actually asks for something `RETURNING` cannot give.
4993#[allow(clippy::too_many_arguments)]
4994async fn reread_returning(
4995    pool: &PgPool,
4996    gql_ctx: &GraphQLContext,
4997    schema_name: &str,
4998    table_name: &str,
4999    type_name: &str,
5000    rows: Vec<Value>,
5001    returning: async_graphql::SelectionField<'_>,
5002    relationships: &HashMap<String, Vec<RelationshipField>>,
5003    names: &crate::names::NameOverrides,
5004    max_rows: Option<i64>,
5005) -> Result<Vec<Value>, async_graphql::Error> {
5006    let guard = gql_ctx
5007        .schema_cache
5008        .get()
5009        .await
5010        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5011    let Some(cache) = guard.as_ref() else {
5012        return Ok(rows);
5013    };
5014    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5015    let Some(table) = cache.get_table(&qi) else {
5016        return Ok(rows);
5017    };
5018    if table.pk_cols.is_empty() {
5019        // Nothing to identify the rows by. The columns are still right.
5020        return Ok(rows);
5021    }
5022
5023    let mut param_idx = 1usize;
5024    let mut values: Vec<serde_json::Value> = Vec::new();
5025    let mut alias_counter = 0usize;
5026    let embeds = build_embed_expressions(
5027        &gql_ctx.caller(),
5028        cache,
5029        relationships,
5030        type_name,
5031        "src",
5032        returning,
5033        max_rows,
5034        &mut alias_counter,
5035        &mut param_idx,
5036        &mut values,
5037        names,
5038    )?;
5039    let computed = computed_projections(
5040        table,
5041        returning,
5042        "src",
5043        names,
5044        cache,
5045        &mut param_idx,
5046        &mut values,
5047    )?;
5048    // A rename is reason enough to read the rows again: `RETURNING` answers in
5049    // the table's own column names, which are not the names the client asked
5050    // under.
5051    let renames = names.renames_columns(&table.schema, &table.name);
5052    if embeds.is_empty() && computed.is_empty() && !renames {
5053        return Ok(rows);
5054    }
5055
5056    // One row per key written, in the order they were written.
5057    let mut conditions = Vec::with_capacity(rows.len());
5058    for row in &rows {
5059        let mut parts = Vec::with_capacity(table.pk_cols.len());
5060        for column in &table.pk_cols {
5061            let Value::Object(map) = row else {
5062                return Ok(rows);
5063            };
5064            let Some(value) = map.get(&async_graphql::Name::new(column)) else {
5065                return Ok(rows);
5066            };
5067            parts.push(format!(
5068                "{} = ${}",
5069                postrust_sql::escape_ident(column),
5070                param_idx
5071            ));
5072            values.push(value_to_json(value));
5073            param_idx += 1;
5074        }
5075        conditions.push(format!("({})", parts.join(" AND ")));
5076    }
5077    if conditions.is_empty() {
5078        return Ok(rows);
5079    }
5080
5081    let mut projection =
5082        rename_projection(table, "src", names).unwrap_or_else(|| "src.*".to_string());
5083    for expression in &computed {
5084        projection.push_str(", ");
5085        projection.push_str(expression);
5086    }
5087    for (name, expression) in &embeds {
5088        projection.push_str(", ");
5089        projection.push_str(expression);
5090        projection.push_str(" AS ");
5091        projection.push_str(&postrust_sql::escape_ident(name));
5092    }
5093
5094    let sql = format!(
5095        "SELECT {} FROM (SELECT {} FROM (SELECT * FROM {}.{} WHERE {}) AS src) AS pgrst_r",
5096        row_json("pgrst_r", &exposed_column_types(table, names)),
5097        projection,
5098        postrust_sql::escape_ident(schema_name),
5099        postrust_sql::escape_ident(table_name),
5100        conditions.join(" OR ")
5101    );
5102
5103    // The same transaction the write went into: the rows it is reading are the
5104    // rows that write produced, and nothing has committed yet.
5105    let mut guard = write_tx(gql_ctx, pool).await?;
5106    let conn = guard.as_mut().expect("write_tx opens one");
5107    let reread = execute_query_on(conn, &sql, &values).await?;
5108    drop(guard);
5109
5110    if reread.is_empty() {
5111        return Ok(rows);
5112    }
5113    Ok(reread.into_iter().map(json_to_value).collect())
5114}
5115
5116/// Execute an insert mutation.
5117async fn execute_insert(
5118    pool: &PgPool,
5119    gql_ctx: &GraphQLContext,
5120    schema_name: &str,
5121    table_name: &str,
5122    objects: serde_json::Value,
5123    context: &InsertContext<'_>,
5124    // Whether the client wrote one row rather than a list of them. Only the
5125    // error paths care: `insert_x_one(object: {...})` has no row to index, so
5126    // its rows are named `args.object` and not `args.object[0]`.
5127    single: bool,
5128) -> Result<(Vec<Value>, usize), async_graphql::Error> {
5129    trace!("Insert mutation for {}: {:?}", table_name, objects);
5130
5131    // Handle both array and single object
5132    let objects_array = match objects {
5133        serde_json::Value::Array(arr) => arr,
5134        serde_json::Value::Object(obj) => vec![serde_json::Value::Object(obj)],
5135        _ => {
5136            return Err(async_graphql::Error::new(
5137                "objects must be an array or object",
5138            ))
5139        }
5140    };
5141
5142    if objects_array.is_empty() {
5143        return Err(async_graphql::Error::new("objects cannot be empty"));
5144    }
5145
5146    let mut guard = write_tx(gql_ctx, pool).await?;
5147    let conn = guard.as_mut().expect("write_tx opens one");
5148
5149    let mut inserted: Vec<Value> = Vec::new();
5150    let mut written = 0usize;
5151
5152    for (index, object) in objects_array.into_iter().enumerate() {
5153        let serde_json::Value::Object(map) = object else {
5154            return Err(async_graphql::Error::new(
5155                "each object to insert is an object",
5156            ));
5157        };
5158        // This row's own place in the request, which is what an error inside
5159        // it is reported against.
5160        let context = InsertContext {
5161            row: match single {
5162                true => context.objects.clone(),
5163                false => format!("{}[{}]", context.objects, index),
5164            },
5165            ..context.clone()
5166        };
5167        let row = insert_row(conn, schema_name, table_name, map, &context, &mut written).await?;
5168        // A row `DO NOTHING` left alone is not in `returning` and is not in
5169        // `affected_rows` either: nothing was written, and the row that was
5170        // already there is not this mutation's to report.
5171        if !row.is_null() {
5172            inserted.push(json_to_value(row));
5173        }
5174    }
5175
5176    Ok((inserted, written))
5177}
5178
5179/// Shape a mutation's result for the field that asked for it.
5180///
5181/// A by-key mutation answers with the row, or with null where the key matched
5182/// nothing. Every other mutation answers with the table's mutation response,
5183/// which carries `affected_rows` alongside the rows themselves -- a client may
5184/// ask for no rows back and still need the count, which is the usual case for
5185/// a delete.
5186///
5187/// The count is passed in rather than taken from the rows, because a nested
5188/// insert writes more rows than it returns: two articles each carrying a new
5189/// author is four rows written and two returned, and four is the answer.
5190fn mutation_result<'a>(rows: Vec<Value>, affected: usize, by_key: bool) -> Option<FieldValue<'a>> {
5191    if by_key {
5192        return rows.into_iter().next().map(FieldValue::value);
5193    }
5194    let mut response = async_graphql::indexmap::IndexMap::new();
5195    response.insert(
5196        async_graphql::Name::new("affected_rows"),
5197        Value::from(affected),
5198    );
5199    response.insert(async_graphql::Name::new("returning"), Value::List(rows));
5200    Some(FieldValue::value(Value::Object(response)))
5201}
5202
5203/// Bind a JSON value to a sqlx query.
5204fn bind_json_value<'q>(
5205    query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
5206    value: &serde_json::Value,
5207) -> sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments> {
5208    match value {
5209        serde_json::Value::Null => query.bind(None::<String>),
5210        serde_json::Value::Bool(b) => query.bind(*b),
5211        serde_json::Value::Number(n) => {
5212            if let Some(i) = n.as_i64() {
5213                query.bind(i)
5214            } else if let Some(f) = n.as_f64() {
5215                query.bind(f)
5216            } else {
5217                query.bind(n.to_string())
5218            }
5219        }
5220        serde_json::Value::String(s) => query.bind(s.clone()),
5221        _ => query.bind(value.to_string()),
5222    }
5223}
5224
5225/// Execute an update mutation.
5226#[allow(clippy::too_many_arguments)] // the predicate needs the schema it reads
5227async fn execute_update(
5228    pool: &PgPool,
5229    gql_ctx: &GraphQLContext,
5230    schema_name: &str,
5231    table_name: &str,
5232    type_name: &str,
5233    operators: Vec<(&'static str, serde_json::Value)>,
5234    column_types: HashMap<String, String>,
5235    where_clause: Option<serde_json::Value>,
5236    relationships: &HashMap<String, Vec<RelationshipField>>,
5237    names: &crate::names::NameOverrides,
5238) -> Result<Vec<Value>, async_graphql::Error> {
5239    use sqlx::Row;
5240
5241    trace!("Update mutation for {}: {:?}", table_name, operators);
5242
5243    // Build the SET clause. Each operator writes a column in terms of itself
5244    // except `_set`, which replaces it -- `_inc` adds, the jsonb operators
5245    // concatenate or remove, and a client may send several in one mutation as
5246    // long as they name different columns.
5247    let mut set_parts: Vec<String> = Vec::new();
5248    let mut set_values: Vec<serde_json::Value> = Vec::new();
5249    let mut param_idx = 1;
5250    let mut written: HashSet<String> = HashSet::new();
5251
5252    // What the permission writes whatever the request said. A preset overrides
5253    // rather than collides -- one a client could override is not a preset --
5254    // so these go in first and the operators below step around the columns
5255    // they name.
5256    //
5257    // This is also what makes an update with no `_set` at all a real update:
5258    // `update_resident(where: {...})` under a permission whose `set` names
5259    // `city` changes `city`, and answers with the rows it changed rather than
5260    // with nothing.
5261    let preset = crate::role::presets(
5262        &gql_ctx.caller(),
5263        names,
5264        schema_name,
5265        table_name,
5266        crate::role::Verb::Update,
5267    )
5268    .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5269    let preset_columns: HashSet<&str> = preset.iter().map(|(name, _)| name.as_str()).collect();
5270    for (column, value) in &preset {
5271        let placeholder =
5272            write_expression(&column_types, column, value, &format!("${}", param_idx));
5273        set_parts.push(format!(
5274            "{} = {}",
5275            postrust_sql::escape_ident(column),
5276            placeholder
5277        ));
5278        set_values.push(write_operand(&column_types, column, value));
5279        param_idx += 1;
5280    }
5281
5282    for (operator, payload) in &operators {
5283        let serde_json::Value::Object(map) = payload else {
5284            return Err(async_graphql::Error::new(format!(
5285                "\"{}\" takes an object mapping columns to values",
5286                operator
5287            )));
5288        };
5289        for (field, value) in map {
5290            let column = &names
5291                .column_source(schema_name, table_name, field)
5292                .unwrap_or(field)
5293                .to_string();
5294            // A preset has this column. Not the duplicate-write error below:
5295            // the request is allowed to name it, and the permission wins.
5296            if preset_columns.contains(column.as_str()) {
5297                continue;
5298            }
5299            if !written.insert(column.clone()) {
5300                return Err(async_graphql::Error::new(format!(
5301                    "\"{}\" is written twice in one update; a column may be \
5302                     changed by one operator at a time",
5303                    column
5304                )));
5305            }
5306            let quoted = postrust_sql::escape_ident(column);
5307            // `_set` writes the column and `_inc` adds to it, so both take a
5308            // value of the column's type and are cast to it -- `money + $1`
5309            // is not an operator PostgreSQL has, and neither is `money +
5310            // double precision`, which is what a bound number arrives as. The
5311            // others already say what they need -- `::jsonb` for a
5312            // concatenation, `::text[]` for a path -- and casting twice would
5313            // be wrong for `_delete_elem`, whose operand is an integer rather
5314            // than a value of the column.
5315            let placeholder = match *operator {
5316                "_set" | "_inc" => {
5317                    write_expression(&column_types, column, value, &format!("${}", param_idx))
5318                }
5319                _ => format!("${}", param_idx),
5320            };
5321            // The assignment PostgreSQL needs, which for everything but `_set`
5322            // reads the column's current value.
5323            let assignment = match *operator {
5324                "_set" => format!("{} = {}", quoted, placeholder),
5325                "_inc" => format!("{} = {} + {}", quoted, quoted, placeholder),
5326                "_append" => format!("{} = {} || {}::jsonb", quoted, quoted, placeholder),
5327                "_prepend" => format!("{} = {}::jsonb || {}", quoted, placeholder, quoted),
5328                "_delete_key" => format!("{} = {} - {}::text", quoted, quoted, placeholder),
5329                "_delete_elem" => format!("{} = {} - {}::int", quoted, quoted, placeholder),
5330                // A path is a list of keys, and one parameter carrying
5331                // `["name","last"]` is a JSON array -- which PostgreSQL reads
5332                // as an array literal and refuses, `malformed array literal`.
5333                // The elements are bound one at a time into an `ARRAY[...]`,
5334                // the same way the key-existence comparisons take theirs.
5335                "_delete_at_path" => {
5336                    let Some(steps) = value.as_array() else {
5337                        return Err(async_graphql::Error::new(format!(
5338                            "\"_delete_at_path\" on \"{}\" takes a list of keys",
5339                            column
5340                        )));
5341                    };
5342                    let path = sql_array(steps, "text[]", &mut param_idx, &mut set_values);
5343                    set_parts.push(format!("{} = {} #- {}", quoted, quoted, path));
5344                    continue;
5345                }
5346                other => {
5347                    return Err(async_graphql::Error::new(format!(
5348                        "unsupported update operator \"{}\"",
5349                        other
5350                    )))
5351                }
5352            };
5353            if *operator == "_set"
5354                && matches!(
5355                    column_types.get(column).map(String::as_str),
5356                    Some("geometry") | Some("geography")
5357                )
5358            {
5359                check_geojson(value)?;
5360            }
5361            set_parts.push(assignment);
5362            set_values.push(match *operator {
5363                "_set" => write_operand(&column_types, column, value),
5364                _ => value.clone(),
5365            });
5366            param_idx += 1;
5367        }
5368    }
5369
5370    // An update that changes nothing changes no rows. Refusing it looks like
5371    // the stricter answer and is the wrong one: a client building `_set` from
5372    // a form the user submitted unchanged sends an empty object, and the
5373    // honest report of what happened is that nothing did.
5374    if set_parts.is_empty() {
5375        return Ok(Vec::new());
5376    }
5377
5378    // Build WHERE clause. It can follow a relationship, the same way a query's
5379    // can: `update_article(where: {author: {name: {_eq: "x"}}})` names the
5380    // rows by something they point at rather than by a column of their own.
5381    let guard = gql_ctx
5382        .schema_cache
5383        .get()
5384        .await
5385        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5386    let cache = guard
5387        .as_ref()
5388        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
5389    let scope = WhereScope::table(schema_name, table_name, type_name, names)
5390        .under_alias(WRITTEN_ROW)
5391        .with_resolution(cache, relationships);
5392    let (where_sql, where_values) = build_where_clause(where_clause.as_ref(), param_idx, &scope)?;
5393
5394    // An absent or unrecognised `where` argument yields an empty clause, which
5395    // would update every row in the table. Refuse instead.
5396    if where_sql.is_empty() {
5397        return Err(async_graphql::Error::new(format!(
5398            "update on \"{}\" requires a `where` argument with at least one \
5399             recognised condition; refusing to update every row",
5400            table_name
5401        )));
5402    }
5403
5404    // What the changed row has to satisfy for the change to stand. Evaluated
5405    // in the `RETURNING` clause, against the row as written -- which is the
5406    // only place it can honestly be evaluated, and costs neither a re-read nor
5407    // a way to identify the rows afterwards.
5408    let (check_sql, check_values) = permission_check_sql(
5409        gql_ctx,
5410        names,
5411        schema_name,
5412        table_name,
5413        crate::role::Verb::Update,
5414        &scope,
5415        param_idx + where_values.len(),
5416    )?;
5417
5418    let sql = format!(
5419        "UPDATE {}.{} AS {} SET {} {} RETURNING {}{}",
5420        postrust_sql::escape_ident(schema_name),
5421        postrust_sql::escape_ident(table_name),
5422        postrust_sql::escape_ident(WRITTEN_ROW),
5423        set_parts.join(", "),
5424        where_sql,
5425        row_json(&postrust_sql::escape_ident(WRITTEN_ROW), &column_types),
5426        check_sql
5427    );
5428
5429    trace!("Executing UPDATE SQL: {}", sql);
5430
5431    // Build query with parameters
5432    let mut query = sqlx::query(&sql);
5433
5434    // Bind SET values
5435    for val in &set_values {
5436        query = bind_json_value(query, val);
5437    }
5438
5439    // Bind WHERE values
5440    for val in &where_values {
5441        query = bind_json_value(query, val);
5442    }
5443
5444    // And the check's, last, because `RETURNING` is.
5445    for val in &check_values {
5446        query = bind_json_value(query, val);
5447    }
5448
5449    let mut guard = write_tx(gql_ctx, pool).await?;
5450    let conn = guard.as_mut().expect("write_tx opens one");
5451    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
5452
5453    refuse_unchecked_rows(&rows, !check_sql.is_empty())?;
5454
5455    let updated: Vec<Value> = rows
5456        .iter()
5457        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
5458        .map(json_to_value)
5459        .collect();
5460
5461    Ok(updated)
5462}
5463
5464/// A permission's check, as a column of the write's `RETURNING`.
5465///
5466/// Empty where nothing has to be checked. Otherwise `, (<predicate>) AS ...`,
5467/// evaluated per written row against the row itself -- so a row the permission
5468/// refuses is known before the transaction is settled, and settling it is
5469/// already a rollback once an error is reported.
5470fn permission_check_sql(
5471    gql_ctx: &GraphQLContext,
5472    names: &crate::names::NameOverrides,
5473    schema_name: &str,
5474    table_name: &str,
5475    verb: crate::role::Verb,
5476    scope: &WhereScope<'_>,
5477    start_param_idx: usize,
5478) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
5479    let check = crate::role::write_check(&gql_ctx.caller(), names, schema_name, table_name, verb)
5480        .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5481    let Some(check) = check else {
5482        return Ok((String::new(), Vec::new()));
5483    };
5484
5485    let mut values = Vec::new();
5486    let mut param_idx = start_param_idx;
5487    let mut alias_counter = 0usize;
5488    let condition = build_condition(
5489        &check,
5490        scope,
5491        &mut param_idx,
5492        &mut values,
5493        &mut alias_counter,
5494    )?;
5495    Ok(match condition {
5496        // A check that compiles to nothing restricts nothing, which is what an
5497        // empty one says.
5498        None => (String::new(), Vec::new()),
5499        Some(sql) => (
5500            format!(
5501                ", ({}) AS {}",
5502                sql,
5503                postrust_sql::escape_ident(CHECK_COLUMN)
5504            ),
5505            values,
5506        ),
5507    })
5508}
5509
5510/// The column a permission's check is returned in.
5511const CHECK_COLUMN: &str = "pgrst_permission_check";
5512
5513/// Refuse the whole write if any row it produced fails the permission's check.
5514///
5515/// Every row, not the first: a mutation is one transaction and a single
5516/// refused row takes the rest with it, which is what makes a check a rule
5517/// about the write rather than about a row. A null counts as a failure -- an
5518/// unknown is not a satisfied condition.
5519fn refuse_unchecked_rows(
5520    rows: &[sqlx::postgres::PgRow],
5521    checked: bool,
5522) -> Result<(), async_graphql::Error> {
5523    if !checked {
5524        return Ok(());
5525    }
5526    use sqlx::Row;
5527    let all_passed = rows
5528        .iter()
5529        .all(|row| row.try_get::<bool, _>(CHECK_COLUMN).unwrap_or(false));
5530    match all_passed {
5531        true => Ok(()),
5532        false => Err(coded_error("permission-error", crate::role::CHECK_FAILED)),
5533    }
5534}
5535
5536/// Execute a delete mutation.
5537#[allow(clippy::too_many_arguments)] // the projection needs the schema it reads
5538async fn execute_delete(
5539    pool: &PgPool,
5540    gql_ctx: &GraphQLContext,
5541    schema_name: &str,
5542    table_name: &str,
5543    type_name: &str,
5544    where_clause: Option<serde_json::Value>,
5545    returning: Option<async_graphql::SelectionField<'_>>,
5546    relationships: &HashMap<String, Vec<RelationshipField>>,
5547    names: &crate::names::NameOverrides,
5548    max_rows: Option<i64>,
5549) -> Result<Vec<Value>, async_graphql::Error> {
5550    use sqlx::Row;
5551
5552    trace!("Delete mutation for {}", table_name);
5553
5554    let guard = gql_ctx
5555        .schema_cache
5556        .get()
5557        .await
5558        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5559    let cache = guard
5560        .as_ref()
5561        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
5562    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5563    let column_types = cache
5564        .get_table(&qi)
5565        .map(|table| exposed_column_types(table, names))
5566        .unwrap_or_default();
5567
5568    // Build WHERE clause. It can follow a relationship -- `delete_article(where:
5569    // {author: {id: {_eq: 2}}})` is the article-by-author delete anyone writes
5570    // -- so the scope is given the schema to resolve one with.
5571    let scope = WhereScope::table(schema_name, table_name, type_name, names)
5572        .under_alias(WRITTEN_ROW)
5573        .with_resolution(cache, relationships);
5574    let (where_sql, mut values) = build_where_clause(where_clause.as_ref(), 1, &scope)?;
5575
5576    // An absent or unrecognised `where` argument yields an empty clause, which
5577    // would delete every row in the table. Refuse instead.
5578    if where_sql.is_empty() {
5579        return Err(async_graphql::Error::new(format!(
5580            "delete on \"{}\" requires a `where` argument with at least one \
5581             recognised condition; refusing to delete every row",
5582            table_name
5583        )));
5584    }
5585
5586    // A relationship or computed field asked for in `returning` cannot be read
5587    // afterwards -- the rows are gone by then, which is why this used to answer
5588    // the plain columns and then fail on a non-null list. Inside one statement
5589    // the deleted rows are still a table: the delete goes in a CTE and the
5590    // projection reads from it, while the rows it points at are still there.
5591    let mut projection = String::new();
5592    let renamed = cache
5593        .get_table(&qi)
5594        .and_then(|table| rename_projection(table, "src", names));
5595    if let Some(returning) = returning {
5596        let mut param_idx = values.len() + 1;
5597        let mut alias_counter = 0usize;
5598        let embeds = build_embed_expressions(
5599            &gql_ctx.caller(),
5600            cache,
5601            relationships,
5602            type_name,
5603            "src",
5604            returning,
5605            max_rows,
5606            &mut alias_counter,
5607            &mut param_idx,
5608            &mut values,
5609            names,
5610        )?;
5611        let computed = match cache.get_table(&qi) {
5612            Some(table) => computed_projections(
5613                table,
5614                returning,
5615                "src",
5616                names,
5617                cache,
5618                &mut param_idx,
5619                &mut values,
5620            )?,
5621            None => Vec::new(),
5622        };
5623        for expression in &computed {
5624            projection.push_str(", ");
5625            projection.push_str(expression);
5626        }
5627        for (name, expression) in &embeds {
5628            projection.push_str(", ");
5629            projection.push_str(expression);
5630            projection.push_str(" AS ");
5631            projection.push_str(&postrust_sql::escape_ident(name));
5632        }
5633    }
5634
5635    let written = postrust_sql::escape_ident(WRITTEN_ROW);
5636    let sql = if projection.is_empty() && renamed.is_none() {
5637        format!(
5638            "DELETE FROM {}.{} AS {} {} RETURNING {}",
5639            postrust_sql::escape_ident(schema_name),
5640            postrust_sql::escape_ident(table_name),
5641            written,
5642            where_sql,
5643            row_json(&written, &column_types)
5644        )
5645    } else {
5646        format!(
5647            "WITH pgrst_deleted AS (DELETE FROM {}.{} AS {} {} RETURNING {}.*) \
5648             SELECT {} FROM (SELECT {}{} FROM pgrst_deleted AS src) AS pgrst_r",
5649            postrust_sql::escape_ident(schema_name),
5650            postrust_sql::escape_ident(table_name),
5651            written,
5652            where_sql,
5653            written,
5654            row_json("pgrst_r", &column_types),
5655            renamed.unwrap_or_else(|| "src.*".to_string()),
5656            projection
5657        )
5658    };
5659
5660    trace!("Executing DELETE SQL: {}", sql);
5661
5662    let mut query = sqlx::query(&sql);
5663    for val in &values {
5664        query = bind_json_value(query, val);
5665    }
5666
5667    let mut guard = write_tx(gql_ctx, pool).await?;
5668    let conn = guard.as_mut().expect("write_tx opens one");
5669    let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
5670
5671    let deleted: Vec<Value> = rows
5672        .iter()
5673        .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
5674        .map(json_to_value)
5675        .collect();
5676
5677    Ok(deleted)
5678}
5679
5680/// The predicate a role's permission adds to every read of one table.
5681///
5682/// `None` where nothing restricts the read: no permission document, an
5683/// administrator, or a permission whose filter is `{}`. The value is a
5684/// `<table>_bool_exp` like any other, because [`crate::role::permission_where`]
5685/// rewrites it into one -- which is what lets the same compiler serve both and
5686/// keeps a permission able to say everything a `where` can.
5687fn permission_predicate(
5688    caller: &crate::role::Caller<'_>,
5689    names: &crate::names::NameOverrides,
5690    schema: &str,
5691    table: &str,
5692) -> Result<Option<serde_json::Value>, async_graphql::Error> {
5693    permission_filter(caller, names, schema, table, crate::role::Verb::Select)
5694}
5695
5696/// The same, for whichever grant the operation is asking under.
5697fn permission_filter(
5698    caller: &crate::role::Caller<'_>,
5699    names: &crate::names::NameOverrides,
5700    schema: &str,
5701    table: &str,
5702    verb: crate::role::Verb,
5703) -> Result<Option<serde_json::Value>, async_graphql::Error> {
5704    crate::role::row_filter(caller, names, schema, table, verb)
5705        .map(|filter| filter.map(|predicate| serde_json::json!({ PERMISSION: predicate })))
5706        .map_err(|fault| coded_error(fault.code(), fault.to_string()))
5707}
5708
5709/// The key a permission's own predicate is wrapped in on its way to the
5710/// compiler.
5711///
5712/// The two halves of a `where` are the same language and are compiled by the
5713/// same code, which is what keeps a permission able to say everything a client
5714/// can. They are not evaluated by the same party: a client's half asks what
5715/// *this caller* may see, and a permission's half is the server's own
5716/// predicate, evaluated as the server.
5717///
5718/// The difference shows on a relationship. `{"Artist": {"id":
5719/// "X-Hasura-Artist-Id"}}` on `Track` grants the tracks belonging to the
5720/// caller's artist -- to a role that has no permission on `Artist` at all, and
5721/// is not meant to have one: the predicate consults that table, it does not
5722/// expose it. Compiled as the caller, it needs a permission that does not
5723/// exist and refuses itself; compiled as the server, it says what it was
5724/// written to say. `_exists` has always worked this way and says so where it
5725/// is built; this is the same rule for the relationship a permission follows.
5726///
5727/// The name cannot collide with a column: no boolean expression a client can
5728/// write has this field, because no input type declares it.
5729const PERMISSION: &str = "_permission";
5730
5731/// How many rows a permission lets one read return.
5732///
5733/// A ceiling rather than a default: a request asking for more gets this, one
5734/// asking for fewer gets what it asked for, and one asking for nothing gets
5735/// the ceiling. The same rule `PGRST_MAX_ROWS` follows, folded with it.
5736fn permission_limit(
5737    caller: &crate::role::Caller<'_>,
5738    names: &crate::names::NameOverrides,
5739    schema: &str,
5740    table: &str,
5741) -> Option<i64> {
5742    crate::role::read_limit(caller, names, schema, table)
5743}
5744
5745/// Both predicates, as one.
5746///
5747/// Written as an `_and` rather than as two clauses so that everything
5748/// downstream -- parameter numbering, relationship resolution, the `EXISTS`
5749/// that a relationship predicate becomes -- happens once, in one place, for
5750/// both halves.
5751fn and_predicates(
5752    request: Option<serde_json::Value>,
5753    permission: Option<serde_json::Value>,
5754) -> Option<serde_json::Value> {
5755    match (request, permission) {
5756        (None, None) => None,
5757        (Some(only), None) | (None, Some(only)) => Some(only),
5758        (Some(request), Some(permission)) => {
5759            Some(serde_json::json!({ "_and": [request, permission] }))
5760        }
5761    }
5762}
5763
5764/// Build a WHERE clause from a boolean expression.
5765///
5766/// The expression is the JSON form of a `<table>_bool_exp`: column names
5767/// mapping to comparisons, and `_and`, `_or` and `_not` for structure. It
5768/// nests, so this recurses.
5769///
5770/// Nothing is dropped silently. An operator that is not recognised widens the
5771/// result set if it is ignored -- every row for a query, every row for a
5772/// mutation -- so it is an error instead.
5773fn build_where_clause(
5774    where_value: Option<&serde_json::Value>,
5775    start_param_idx: usize,
5776    scope: &WhereScope<'_>,
5777) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
5778    let mut values: Vec<serde_json::Value> = Vec::new();
5779    let mut param_idx = start_param_idx;
5780    let mut alias_counter = 0usize;
5781
5782    let condition = match where_value {
5783        Some(value) => build_condition(
5784            value,
5785            scope,
5786            &mut param_idx,
5787            &mut values,
5788            &mut alias_counter,
5789        )?,
5790        None => None,
5791    };
5792
5793    Ok(match condition {
5794        Some(sql) => (format!("WHERE {}", sql), values),
5795        None => (String::new(), values),
5796    })
5797}
5798
5799/// The table a boolean expression is being read against.
5800///
5801/// Column references are qualified with `sql_ref` so that a predicate over a
5802/// relationship -- which becomes a correlated `EXISTS` against another table --
5803/// can tell the two apart. Without the qualification a child column sharing a
5804/// parent column's name would silently resolve to whichever PostgreSQL
5805/// preferred.
5806///
5807/// `resolution` is absent where a caller has no schema cache to hand, which is
5808/// how the mutation paths are called. A relationship predicate then reports
5809/// that it cannot be resolved rather than being read as a column.
5810pub struct WhereScope<'a> {
5811    /// The table itself, for the comparisons that need a column's type.
5812    qualified: postrust_core::api_request::QualifiedIdentifier,
5813    /// How to refer to this table's columns in SQL.
5814    sql_ref: String,
5815    /// How to refer to this table's *row*, which is what a function taking the
5816    /// row is passed. A qualified name reads as a column reference there, so
5817    /// this is the bare alias.
5818    row_ref: String,
5819    /// The GraphQL type name, which is the key into the relationship map.
5820    type_name: String,
5821    /// How to refer to the columns of the table the predicate started at, and
5822    /// which table that is.
5823    ///
5824    /// A column comparison written `["$", "name"]` means that table's column,
5825    /// however many relationships the predicate has descended through since --
5826    /// which is what makes the form worth having. Equal to this table's own
5827    /// until an `EXISTS` is entered.
5828    root_ref: String,
5829    root_qualified: postrust_core::api_request::QualifiedIdentifier,
5830    /// The names this table's columns are exposed under, so a predicate
5831    /// written against a field can be written as SQL against a column.
5832    names: &'a crate::names::NameOverrides,
5833    resolution: Option<WhereResolution<'a>>,
5834    /// Who is asking, so that a predicate following a relationship narrows the
5835    /// far side to what that caller may read.
5836    ///
5837    /// Unrestricted unless a scope is told otherwise, which is what leaves
5838    /// every path that has no caller to hand -- the mutation ones -- behaving
5839    /// as it did.
5840    caller: Option<crate::role::Caller<'a>>,
5841}
5842
5843/// What a scope needs to follow a relationship.
5844struct WhereResolution<'a> {
5845    cache: &'a SchemaCache,
5846    relationships: &'a HashMap<String, Vec<RelationshipField>>,
5847}
5848
5849impl<'a> WhereScope<'a> {
5850    /// A scope over a table addressed by its qualified name.
5851    pub fn table(
5852        schema: &str,
5853        table: &str,
5854        type_name: &str,
5855        names: &'a crate::names::NameOverrides,
5856    ) -> Self {
5857        Self {
5858            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
5859            sql_ref: format!(
5860                "{}.{}",
5861                postrust_sql::escape_ident(schema),
5862                postrust_sql::escape_ident(table)
5863            ),
5864            row_ref: postrust_sql::escape_ident(table),
5865            type_name: type_name.to_string(),
5866            root_ref: format!(
5867                "{}.{}",
5868                postrust_sql::escape_ident(schema),
5869                postrust_sql::escape_ident(table)
5870            ),
5871            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
5872            names,
5873            resolution: None,
5874            caller: None,
5875        }
5876    }
5877
5878    /// The same, referred to by an alias rather than by its name.
5879    ///
5880    /// A statement that writes a table has to name that table's row in
5881    /// `RETURNING`, and a bare table name there is read as a *column* first --
5882    /// so `area.area`, a geography column in a table of the same name, made
5883    /// `to_jsonb("area")` the shape rather than the row. The alias is a name
5884    /// no column has.
5885    fn under_alias(mut self, alias: &str) -> Self {
5886        // The root moves with it: this is the outermost table being renamed,
5887        // not a step into another one.
5888        if self.root_ref == self.sql_ref {
5889            self.root_ref = postrust_sql::escape_ident(alias);
5890        }
5891        self.sql_ref = postrust_sql::escape_ident(alias);
5892        self.row_ref = postrust_sql::escape_ident(alias);
5893        self
5894    }
5895
5896    /// The same, able to follow relationships.
5897    fn with_resolution(
5898        mut self,
5899        cache: &'a SchemaCache,
5900        relationships: &'a HashMap<String, Vec<RelationshipField>>,
5901    ) -> Self {
5902        self.resolution = Some(WhereResolution {
5903            cache,
5904            relationships,
5905        });
5906        self
5907    }
5908
5909    /// A scope over an aliased table, able to follow relationships.
5910    #[allow(clippy::too_many_arguments)]
5911    fn for_alias(
5912        schema: &str,
5913        table: &str,
5914        alias: &str,
5915        type_name: &str,
5916        cache: &'a SchemaCache,
5917        relationships: &'a HashMap<String, Vec<RelationshipField>>,
5918        names: &'a crate::names::NameOverrides,
5919    ) -> Self {
5920        Self {
5921            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
5922            sql_ref: postrust_sql::escape_ident(alias),
5923            row_ref: postrust_sql::escape_ident(alias),
5924            type_name: type_name.to_string(),
5925            root_ref: postrust_sql::escape_ident(alias),
5926            root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
5927            names,
5928            resolution: Some(WhereResolution {
5929                cache,
5930                relationships,
5931            }),
5932            caller: None,
5933        }
5934    }
5935
5936    /// A scope over an aliased table, for the inside of an `EXISTS`.
5937    /// A scope over the *other* table of a relationship, under an alias.
5938    ///
5939    /// The child's own qualified name, not the parent's: a comparison inside
5940    /// an `EXISTS` is against the child's columns, so it is the child's types
5941    /// and the child's renames that decide how to write it.
5942    fn aliased(
5943        schema: &str,
5944        table: &str,
5945        alias: &str,
5946        type_name: &str,
5947        from: &WhereScope<'a>,
5948    ) -> Self {
5949        Self {
5950            qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
5951            sql_ref: postrust_sql::escape_ident(alias),
5952            row_ref: postrust_sql::escape_ident(alias),
5953            type_name: type_name.to_string(),
5954            // Carried, not replaced: `$` names the table the predicate began
5955            // at, which is the whole point of descending with it.
5956            root_ref: from.root_ref.clone(),
5957            root_qualified: from.root_qualified.clone(),
5958            names: from.names,
5959            resolution: from.resolution.as_ref().map(|r| WhereResolution {
5960                cache: r.cache,
5961                relationships: r.relationships,
5962            }),
5963            // Carried across the relationship: the caller does not change on
5964            // the way into an EXISTS, and the far side has its own permission
5965            // to be narrowed by.
5966            caller: from.caller,
5967        }
5968    }
5969
5970    /// Narrow what this scope's predicates may reach to one caller.
5971    pub fn for_caller(mut self, caller: crate::role::Caller<'a>) -> Self {
5972        self.caller = Some(caller);
5973        self
5974    }
5975
5976    /// The same scope with no caller: what a permission's own predicate is
5977    /// evaluated as. See `PERMISSION`.
5978    fn as_system(&self) -> WhereScope<'a> {
5979        WhereScope {
5980            qualified: self.qualified.clone(),
5981            sql_ref: self.sql_ref.clone(),
5982            row_ref: self.row_ref.clone(),
5983            type_name: self.type_name.clone(),
5984            // The root moves here. `$` in a permission names the table the
5985            // permission is *written on*, which is this one -- not whatever
5986            // table the query happened to start at. Reading `dml_rbp_tasks`
5987            // through a project's `where: {tasks: {...}}` compared the task's
5988            // grant against a column of `dml_rbp_projects` until this, and
5989            // answered with rows the role may not see.
5990            root_ref: self.sql_ref.clone(),
5991            root_qualified: self.qualified.clone(),
5992            names: self.names,
5993            resolution: self.resolution.as_ref().map(|r| WhereResolution {
5994                cache: r.cache,
5995                relationships: r.relationships,
5996            }),
5997            caller: None,
5998        }
5999    }
6000
6001    /// What a field name refers to, as SQL: a column of this table, or the
6002    /// call that produces a computed field.
6003    ///
6004    /// A computed field is in the boolean expression because it is on the type
6005    /// -- `where: {sum_float_offset: {_gt: 5}}` is a question a client can
6006    /// write -- and reading it as a column answered `column
6007    /// float_test.sum_float_offset does not exist`.
6008    fn column(&self, name: &str) -> String {
6009        let column = self.column_name(name);
6010        let plain = format!("{}.{}", self.sql_ref, postrust_sql::escape_ident(column));
6011        let Some(cache) = self.resolution.as_ref().map(|r| r.cache) else {
6012            return plain;
6013        };
6014        let Some(table) = cache.get_table(&self.qualified) else {
6015            return plain;
6016        };
6017        if table.get_column(column).is_some() {
6018            return plain;
6019        }
6020        let function = self
6021            .names
6022            .computed_source(&table.schema, &table.name, name)
6023            .unwrap_or(name);
6024        match table.get_computed_column(function) {
6025            Some(definition) => {
6026                computed_call(definition, &format!("{}.*", self.row_ref), &self.row_ref)
6027            }
6028            None => plain,
6029        }
6030    }
6031
6032    /// The column behind a field name.
6033    fn column_name<'n>(&'n self, name: &'n str) -> &'n str {
6034        self.names
6035            .column_source(&self.qualified.schema, &self.qualified.name, name)
6036            .unwrap_or(name)
6037    }
6038
6039    /// The PostgreSQL type of one of this table's columns, where it can be
6040    /// found. A spatial comparison needs it: the same function takes a
6041    /// geometry or a geography and the operand has to be cast to match.
6042    fn column_type(&self, name: &str) -> Option<String> {
6043        let cache = self.resolution.as_ref()?.cache;
6044        let table = cache.get_table(&self.qualified)?;
6045        table
6046            .get_column(self.column_name(name))
6047            .map(|c| c.nominal_type.clone())
6048    }
6049
6050    /// The other side of a column-to-column comparison, as SQL.
6051    ///
6052    /// Hasura writes the operand two ways. A bare name is a column of *this*
6053    /// table -- `{"bid_price": {"$cgt": "price"}}` compares two columns of one
6054    /// auction. `["$", "name"]` is a column of the table the permission is
6055    /// written on, which is what makes the form useful inside a predicate over
6056    /// a relationship: `{"item": {"item_quantity": {"_cgte": ["$",
6057    /// "order_cart_quantity"]}}}` asks whether the item has as many in stock
6058    /// as the cart is asking for, and the cart is the outer row.
6059    ///
6060    /// Nothing else is accepted. A longer path would walk relationships to
6061    /// reach the column, which Hasura allows and no case here writes -- and
6062    /// reading it wrongly would widen a permission rather than narrow it.
6063    fn other_column(
6064        &self,
6065        operand: &serde_json::Value,
6066        op: &str,
6067    ) -> Result<String, async_graphql::Error> {
6068        use serde_json::Value;
6069
6070        let refused = |what: &str| {
6071            async_graphql::Error::new(format!(
6072                "the \"{}\" comparison takes the name of a column, {}",
6073                op, what
6074            ))
6075        };
6076        let (against, qualified, name) = match operand {
6077            Value::String(name) => (&self.sql_ref, &self.qualified, name.as_str()),
6078            Value::Array(path) => match path.as_slice() {
6079                [Value::String(name)] => (&self.sql_ref, &self.qualified, name.as_str()),
6080                [Value::String(root), Value::String(name)] if root == "$" => {
6081                    (&self.root_ref, &self.root_qualified, name.as_str())
6082                }
6083                _ => return Err(refused("or [\"$\", \"a column\"]")),
6084            },
6085            _ => return Err(refused("written as a string")),
6086        };
6087        // Read against whichever table it belongs to, since a rename is
6088        // per-table and the two are not the same table.
6089        let column = self
6090            .names
6091            .column_source(&qualified.schema, &qualified.name, name)
6092            .unwrap_or(name);
6093        Ok(format!(
6094            "{}.{}",
6095            against,
6096            postrust_sql::escape_ident(column)
6097        ))
6098    }
6099
6100    /// The relationship a predicate is following, where there is one.
6101    ///
6102    /// Looked up in the schema's own map first, which is the answer for every
6103    /// relationship the role can read. A permission may name one it cannot:
6104    /// role `Artist` reads `Track` and filters it by `{"Artist": {"id": ...}}`
6105    /// while having no permission on `Artist` at all, so that relationship is
6106    /// in no schema this role is served. The catalogue still has it -- the
6107    /// cache a scope resolves against is the unreduced one -- and the
6108    /// relationship is derived from it there.
6109    ///
6110    /// Deriving it cannot let a client reach a hidden table: a key that is not
6111    /// a field of the boolean expression is refused before any of this runs,
6112    /// and the only writer of such a key is a permission.
6113    fn relationship(&self, name: &str) -> Option<std::borrow::Cow<'a, RelationshipField>> {
6114        let resolution = self.resolution.as_ref()?;
6115        let published = resolution
6116            .relationships
6117            .get(&self.type_name)
6118            .and_then(|fields| fields.iter().find(|r| r.name == name));
6119        if let Some(found) = published {
6120            return Some(std::borrow::Cow::Borrowed(found));
6121        }
6122
6123        let table = resolution.cache.get_table(&self.qualified)?;
6124
6125        // A relationship metadata declares, which may be a mapped join the
6126        // catalogue has never heard of. `message.members` is one, and a
6127        // permission on `message` is written through it -- so this is not a
6128        // convenience: without it that filter cannot be compiled at all and
6129        // the read is refused rather than narrowed.
6130        if let Some(declared) = self
6131            .names
6132            .declared_relationships(&table.schema, &table.name)
6133        {
6134            if let Some(entry) = declared.iter().find(|entry| entry.name == name) {
6135                // Reached by a mapping, which the catalogue has never heard
6136                // of.
6137                if let Some(field) = crate::schema::mapped_relationship_field(
6138                    entry,
6139                    table,
6140                    resolution.cache,
6141                    &table.schema,
6142                    &entry
6143                        .target(&table.schema)
6144                        .map(|(_, target)| target)
6145                        .unwrap_or_default(),
6146                ) {
6147                    return Some(std::borrow::Cow::Owned(field));
6148                }
6149                // Or by a key, under the name the declaration gives it --
6150                // which is the only place that name is written down now that
6151                // a declaration carries it.
6152                if let Some(key) = entry.using.as_deref() {
6153                    if let Some(found) = resolution
6154                        .cache
6155                        .get_relationships(&self.qualified, &table.schema)
6156                        .into_iter()
6157                        .flatten()
6158                        .find(|rel| {
6159                            crate::schema::relationship_keys(rel)
6160                                .iter()
6161                                .any(|found| found == key)
6162                        })
6163                    {
6164                        return Some(std::borrow::Cow::Owned(
6165                            crate::schema::relationship::RelationshipField::from_relationship_named(
6166                                found,
6167                                &found.foreign_table().name,
6168                                Some(&entry.name),
6169                            ),
6170                        ));
6171                    }
6172                }
6173            }
6174        }
6175
6176        resolution
6177            .cache
6178            .get_relationships(&self.qualified, &table.schema)?
6179            .iter()
6180            .find_map(|rel| {
6181                let given = crate::schema::relationship_keys(rel)
6182                    .iter()
6183                    .find_map(|key| self.names.relationship(&table.schema, &table.name, key));
6184                // The target's own name stands in for its GraphQL type, which
6185                // this role has none of. Nothing reads it but the lookup
6186                // above, and that lookup will land here again.
6187                let field = RelationshipField::from_relationship_named(
6188                    rel,
6189                    &rel.foreign_table().name,
6190                    given,
6191                );
6192                (field.name == name).then_some(std::borrow::Cow::Owned(field))
6193            })
6194    }
6195}
6196
6197/// The SQL operator behind a column-to-column comparison, where this is one.
6198///
6199/// `_ceq`, `_cne`, `_cgt`, `_cgte`, `_clt`, `_clte`: the `c` is for column.
6200/// Only a permission writes these -- no boolean expression a client can write
6201/// has them, because a client has no way to name the row a comparison would be
6202/// against. A permission does: `{"bid_price": {"$cgt": "price"}}` on `auction`
6203/// grants the rows whose bid has passed the reserve, which is a fact about the
6204/// row rather than about the caller.
6205fn column_comparison(op: &str) -> Option<&'static str> {
6206    Some(match op {
6207        "_ceq" => "=",
6208        "_cne" => "<>",
6209        "_cgt" => ">",
6210        "_cgte" => ">=",
6211        "_clt" => "<",
6212        "_clte" => "<=",
6213        _ => return None,
6214    })
6215}
6216
6217/// One boolean expression, as SQL. `None` where it constrains nothing.
6218fn build_condition(
6219    value: &serde_json::Value,
6220    scope: &WhereScope<'_>,
6221    param_idx: &mut usize,
6222    values: &mut Vec<serde_json::Value>,
6223    alias_counter: &mut usize,
6224) -> Result<Option<String>, async_graphql::Error> {
6225    let serde_json::Value::Object(map) = value else {
6226        return Ok(None);
6227    };
6228
6229    let mut conditions: Vec<String> = Vec::new();
6230
6231    for (key, val) in map {
6232        match key.as_str() {
6233            // An empty `_and` is true and an empty `_or` is false, which is
6234            // what SQL's own identities give: nothing to AND constrains
6235            // nothing, nothing to OR matches nothing.
6236            "_and" | "_or" => {
6237                let members = val.as_array().ok_or_else(|| {
6238                    async_graphql::Error::new(format!("\"{}\" takes a list of expressions", key))
6239                })?;
6240                let mut parts = Vec::new();
6241                for member in members {
6242                    if let Some(sql) =
6243                        build_condition(member, scope, param_idx, values, alias_counter)?
6244                    {
6245                        parts.push(sql);
6246                    }
6247                }
6248                if parts.is_empty() {
6249                    if key == "_or" {
6250                        conditions.push("false".to_string());
6251                    }
6252                    continue;
6253                }
6254                let joiner = if key == "_and" { " AND " } else { " OR " };
6255                conditions.push(format!("({})", parts.join(joiner)));
6256            }
6257            "_not" => {
6258                if let Some(sql) = build_condition(val, scope, param_idx, values, alias_counter)? {
6259                    conditions.push(format!("NOT ({})", sql));
6260                }
6261            }
6262            // A permission's own predicate, which is the server's rather
6263            // than the caller's. See `PERMISSION`.
6264            PERMISSION => {
6265                if let Some(sql) =
6266                    build_condition(val, &scope.as_system(), param_idx, values, alias_counter)?
6267                {
6268                    conditions.push(sql);
6269                }
6270            }
6271            // A question about a table nothing relates this one to. Only a
6272            // permission writes it; no client can, because no boolean
6273            // expression has the field.
6274            "_exists" => {
6275                conditions.push(table_exists_sql(
6276                    val,
6277                    scope,
6278                    param_idx,
6279                    values,
6280                    alias_counter,
6281                )?);
6282            }
6283            // A relationship is filtered by filtering the rows at its other
6284            // end. `where: {articles: {title: {_eq: "x"}}}` keeps the authors
6285            // that have such an article -- an `EXISTS` correlated back to the
6286            // parent row, which is also what it means for a to-one
6287            // relationship, where the subquery simply cannot match twice.
6288            name if scope.relationship(name).is_some() => {
6289                let relationship = scope.relationship(name).expect("just checked");
6290                conditions.push(exists_sql(
6291                    &relationship,
6292                    val,
6293                    scope,
6294                    param_idx,
6295                    values,
6296                    alias_counter,
6297                )?);
6298            }
6299            // A question about the whole related set rather than about any
6300            // one row of it. `where: {articles_aggregate: {count: {predicate:
6301            // {_gt: 2}}}}` keeps the authors with more than two articles,
6302            // which no `EXISTS` over one article can say.
6303            name if scope.column_type(name).is_none()
6304                && name
6305                    .strip_suffix("_aggregate")
6306                    .and_then(|rel| scope.relationship(rel))
6307                    .is_some_and(|rel| rel.is_list) =>
6308            {
6309                let relationship = name
6310                    .strip_suffix("_aggregate")
6311                    .and_then(|rel| scope.relationship(rel))
6312                    .expect("just checked");
6313                conditions.push(aggregate_predicate_sql(
6314                    &relationship,
6315                    val,
6316                    scope,
6317                    param_idx,
6318                    values,
6319                    alias_counter,
6320                )?);
6321            }
6322            column => {
6323                let quoted = scope.column(column);
6324                match val {
6325                    serde_json::Value::Object(ops) => {
6326                        let column_type = scope.column_type(column);
6327                        for (op, operand) in ops {
6328                            // A comparison against another column rather than
6329                            // against a value, which only a permission can
6330                            // write. It needs the scope to say *which* column,
6331                            // so it is answered here rather than in
6332                            // `comparison_sql`, which sees one side.
6333                            if let Some(other) = column_comparison(op) {
6334                                conditions.push(format!(
6335                                    "{} {} {}",
6336                                    quoted,
6337                                    other,
6338                                    scope.other_column(operand, op)?
6339                                ));
6340                                continue;
6341                            }
6342                            conditions.push(comparison_sql(
6343                                &quoted,
6344                                column,
6345                                column_type.as_deref(),
6346                                op,
6347                                operand,
6348                                param_idx,
6349                                values,
6350                            )?);
6351                        }
6352                    }
6353                    // `{id: 1}` rather than `{id: {_eq: 1}}`. Not a spelling
6354                    // Hasura accepts, but one that costs nothing to read and
6355                    // that hand-written queries reach for.
6356                    other => {
6357                        conditions.push(format!("{} = ${}", quoted, param_idx));
6358                        values.push(other.clone());
6359                        *param_idx += 1;
6360                    }
6361                }
6362            }
6363        }
6364    }
6365
6366    Ok(if conditions.is_empty() {
6367        None
6368    } else if conditions.len() == 1 {
6369        conditions.pop()
6370    } else {
6371        Some(conditions.join(" AND "))
6372    })
6373}
6374
6375/// A predicate over a relationship, as a correlated `EXISTS`.
6376///
6377/// Only a plain key join is expressible this way. A many-to-many through a
6378/// junction and a computed relationship both need more than a pair of columns
6379/// to correlate on, and saying so is the only honest answer -- reading the
6380/// predicate as though it constrained nothing would return every parent row.
6381fn exists_sql(
6382    relationship: &RelationshipField,
6383    child_expression: &serde_json::Value,
6384    scope: &WhereScope<'_>,
6385    param_idx: &mut usize,
6386    values: &mut Vec<serde_json::Value>,
6387    alias_counter: &mut usize,
6388) -> Result<String, async_graphql::Error> {
6389    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
6390        async_graphql::Error::new(format!(
6391            "filtering on the relationship \"{}\" is not available here",
6392            relationship.name
6393        ))
6394    })?;
6395
6396    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
6397        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6398
6399    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
6400        return Err(async_graphql::Error::new(format!(
6401            "filtering on \"{}\" is not supported: it is reached through a junction \
6402             rather than by a key",
6403            relationship.name
6404        )));
6405    }
6406
6407    *alias_counter += 1;
6408    let alias = format!("pgrst_rel_{}", alias_counter);
6409    let child_scope = WhereScope::aliased(
6410        &plan.foreign_schema,
6411        &plan.foreign_table,
6412        &alias,
6413        &relationship.target_type,
6414        scope,
6415    );
6416
6417    // A computed relationship is correlated by argument -- the function takes
6418    // the parent row -- where a key relationship is correlated by columns.
6419    let (source, mut correlation) = match &plan.function {
6420        Some(function) => (
6421            format!(
6422                "{}.{}({})",
6423                postrust_sql::escape_ident(&function.schema),
6424                postrust_sql::escape_ident(&function.name),
6425                scope.row_ref
6426            ),
6427            Vec::new(),
6428        ),
6429        None => {
6430            let mut columns = Vec::with_capacity(plan.columns.len());
6431            for (parent_column, child_column) in &plan.columns {
6432                columns.push(format!(
6433                    "{} = {}",
6434                    scope.column(parent_column),
6435                    child_scope.column(child_column)
6436                ));
6437            }
6438            (
6439                format!(
6440                    "{}.{}",
6441                    postrust_sql::escape_ident(&plan.foreign_schema),
6442                    postrust_sql::escape_ident(&plan.foreign_table)
6443                ),
6444                columns,
6445            )
6446        }
6447    };
6448
6449    let child_condition = build_condition(
6450        child_expression,
6451        &child_scope,
6452        param_idx,
6453        values,
6454        alias_counter,
6455    )?;
6456    if let Some(sql) = child_condition {
6457        correlation.push(format!("({})", sql));
6458    }
6459
6460    // The far side's own permission. `where: {author: {name: {_eq: "Ann"}}}`
6461    // asks whether a related row exists, and a row this caller may not read is
6462    // one it may not learn the existence of either -- so the child's filter
6463    // narrows the EXISTS exactly as it narrows a read of the same table.
6464    if let Some(caller) = &child_scope.caller {
6465        // Through `permission_predicate` rather than `role::read_predicate`,
6466        // so the far side's filter is marked as the server's own -- see
6467        // `PERMISSION`. Without the mark it was compiled as the caller, and a
6468        // filter that follows a relationship of its own then needed a
6469        // permission on *that* table: `dml_rbp_tasks` is filtered through
6470        // `grants`, which the role reading tasks may not read.
6471        let permission = permission_predicate(
6472            caller,
6473            child_scope.names,
6474            &plan.foreign_schema,
6475            &plan.foreign_table,
6476        )?;
6477        if let Some(predicate) = permission {
6478            if let Some(sql) =
6479                build_condition(&predicate, &child_scope, param_idx, values, alias_counter)?
6480            {
6481                correlation.push(format!("({})", sql));
6482            }
6483        }
6484    }
6485    if correlation.is_empty() {
6486        correlation.push("true".to_string());
6487    }
6488
6489    Ok(format!(
6490        "EXISTS (SELECT 1 FROM {} AS {} WHERE {})",
6491        source,
6492        postrust_sql::escape_ident(&alias),
6493        correlation.join(" AND ")
6494    ))
6495}
6496
6497/// `_exists`, the predicate only a permission can write, as SQL.
6498///
6499/// ```json
6500/// {"_exists": {"_table": "user", "_where": {"id": "X-Hasura-User-Id",
6501///                                           "is_admin": true}}}
6502/// ```
6503///
6504/// It asks whether a row exists in *another* table -- one no foreign key
6505/// relates to this one. That is how Hasura writes "the caller is an
6506/// administrator" without asking the caller to say so: the row that decides it
6507/// lives in a table of its own, and whether the account this predicate guards
6508/// is readable does not depend on which account it is.
6509///
6510/// So the subselect is **uncorrelated**, which is the whole difference from
6511/// the `EXISTS` a relationship predicate builds. There are no key columns to
6512/// join on -- if there were, the permission would have been written as a
6513/// relationship -- and the `_where` is read against the named table's columns
6514/// alone.
6515///
6516/// The caller's own permissions are not applied inside it, and that is not an
6517/// oversight. The point of `_exists` is to consult a table the role has no
6518/// access to: in Hasura's corpus the role that reads `account` through this
6519/// predicate may not read `public.user` at all. Narrowing the subselect to
6520/// what the caller may read would make the predicate refuse itself.
6521fn table_exists_sql(
6522    spec: &serde_json::Value,
6523    scope: &WhereScope<'_>,
6524    param_idx: &mut usize,
6525    values: &mut Vec<serde_json::Value>,
6526    alias_counter: &mut usize,
6527) -> Result<String, async_graphql::Error> {
6528    let Some((schema, table)) = crate::role::exists_target(spec, &scope.qualified.schema) else {
6529        return Err(async_graphql::Error::new(
6530            "\"_exists\" needs a \"_table\" to look in",
6531        ));
6532    };
6533
6534    // The name comes from a permission document rather than from a request,
6535    // but it still reaches SQL, and a table that is not there would reach it as
6536    // a syntax error at the far end rather than as an answer here.
6537    let qualified = postrust_core::api_request::QualifiedIdentifier::new(&schema, &table);
6538    if scope
6539        .resolution
6540        .as_ref()
6541        .is_some_and(|r| r.cache.get_table(&qualified).is_none())
6542    {
6543        return Err(async_graphql::Error::new(format!(
6544            "\"_exists\" names a table this server does not have: \"{}.{}\"",
6545            schema, table
6546        )));
6547    }
6548
6549    *alias_counter += 1;
6550    let alias = format!("pgrst_exists_{}", alias_counter);
6551    // The type this table is exposed as, which is the key into the
6552    // relationship map -- so a `_where` may follow the named table's own
6553    // relationships, the way any other predicate over it could.
6554    let type_name = scope
6555        .names
6556        .base_name(&schema, &table)
6557        .unwrap_or(&table)
6558        .to_string();
6559    let mut inner = WhereScope::aliased(&schema, &table, &alias, &type_name, scope);
6560    // See above: the predicate is the permission, so it is not itself
6561    // subject to one.
6562    inner.caller = None;
6563
6564    let condition = match spec.get("_where") {
6565        Some(predicate) => build_condition(predicate, &inner, param_idx, values, alias_counter)?,
6566        None => None,
6567    };
6568
6569    Ok(format!(
6570        "EXISTS (SELECT 1 FROM {}.{} AS {} WHERE {})",
6571        postrust_sql::escape_ident(&schema),
6572        postrust_sql::escape_ident(&table),
6573        postrust_sql::escape_ident(&alias),
6574        condition.unwrap_or_else(|| "true".to_string())
6575    ))
6576}
6577
6578/// A predicate over an aggregate of a related set, as SQL.
6579///
6580/// `{count: {predicate: {_gt: 2}, filter: {...}}}` becomes a scalar subselect
6581/// correlated back to the parent row, compared the way any column is:
6582///
6583/// ```text
6584/// (SELECT count(*) FROM "public"."article" AS pgrst_rel_1
6585///   WHERE "author"."id" = pgrst_rel_1."author_id" AND (...)) > $1
6586/// ```
6587///
6588/// A scalar subselect rather than an `EXISTS`, because the answer is a number
6589/// or a truth value and not whether a row is there -- and because over no rows
6590/// at all `count` is zero and `bool_and` is null, which is what SQL says and
6591/// what a client asking "no articles" is relying on.
6592fn aggregate_predicate_sql(
6593    relationship: &RelationshipField,
6594    expression: &serde_json::Value,
6595    scope: &WhereScope<'_>,
6596    param_idx: &mut usize,
6597    values: &mut Vec<serde_json::Value>,
6598    alias_counter: &mut usize,
6599) -> Result<String, async_graphql::Error> {
6600    let serde_json::Value::Object(over) = expression else {
6601        return Ok("true".to_string());
6602    };
6603
6604    let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
6605        async_graphql::Error::new(format!(
6606            "filtering on the relationship \"{}\" is not available here",
6607            relationship.name
6608        ))
6609    })?;
6610    let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
6611        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6612    if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
6613        return Err(async_graphql::Error::new(format!(
6614            "aggregating over \"{}\" is not supported: it is reached through a junction \
6615             rather than by a key",
6616            relationship.name
6617        )));
6618    }
6619
6620    let mut conditions: Vec<String> = Vec::new();
6621    for (function, spec) in over {
6622        if spec.is_null() {
6623            continue;
6624        }
6625        let serde_json::Value::Object(spec) = spec else {
6626            continue;
6627        };
6628        let Some(predicate) = spec.get("predicate").filter(|p| !p.is_null()) else {
6629            return Err(validation_error(format!(
6630                "\"{}\" over \"{}\" needs a predicate",
6631                function, relationship.name
6632            )));
6633        };
6634
6635        *alias_counter += 1;
6636        let alias = format!("pgrst_agg_{}", alias_counter);
6637        let child_scope = WhereScope::aliased(
6638            &plan.foreign_schema,
6639            &plan.foreign_table,
6640            &alias,
6641            &relationship.target_type,
6642            scope,
6643        );
6644
6645        // Correlated the way an `EXISTS` over the same relationship is: by
6646        // the key, or by handing the parent row to the function.
6647        let (source, mut correlation) = match &plan.function {
6648            Some(routine) => (
6649                format!(
6650                    "{}.{}({})",
6651                    postrust_sql::escape_ident(&routine.schema),
6652                    postrust_sql::escape_ident(&routine.name),
6653                    scope.row_ref
6654                ),
6655                Vec::new(),
6656            ),
6657            None => {
6658                let mut columns = Vec::with_capacity(plan.columns.len());
6659                for (parent_column, child_column) in &plan.columns {
6660                    columns.push(format!(
6661                        "{} = {}",
6662                        scope.column(parent_column),
6663                        child_scope.column(child_column)
6664                    ));
6665                }
6666                (
6667                    format!(
6668                        "{}.{}",
6669                        postrust_sql::escape_ident(&plan.foreign_schema),
6670                        postrust_sql::escape_ident(&plan.foreign_table)
6671                    ),
6672                    columns,
6673                )
6674            }
6675        };
6676        // `filter` narrows what is aggregated, and is an ordinary boolean
6677        // expression over the child.
6678        if let Some(filter) = spec.get("filter").filter(|f| !f.is_null()) {
6679            if let Some(sql) =
6680                build_condition(filter, &child_scope, param_idx, values, alias_counter)?
6681            {
6682                correlation.push(format!("({})", sql));
6683            }
6684        }
6685        if correlation.is_empty() {
6686            correlation.push("true".to_string());
6687        }
6688
6689        let arguments = spec.get("arguments").filter(|a| !a.is_null());
6690        let distinct = matches!(spec.get("distinct"), Some(serde_json::Value::Bool(true)));
6691        let (aggregate, operand_type) = match function.as_str() {
6692            "count" => {
6693                let columns: Vec<String> = match arguments {
6694                    Some(serde_json::Value::Array(items)) => items
6695                        .iter()
6696                        .filter_map(|item| item.as_str())
6697                        .map(|column| child_scope.column(column))
6698                        .collect(),
6699                    Some(serde_json::Value::String(one)) => vec![child_scope.column(one)],
6700                    _ => Vec::new(),
6701                };
6702                let counted = match columns.len() {
6703                    0 => "*".to_string(),
6704                    1 => columns.into_iter().next().unwrap_or_default(),
6705                    _ => format!("({})", columns.join(", ")),
6706                };
6707                let counted = match distinct && counted != "*" {
6708                    true => format!("DISTINCT {}", counted),
6709                    false => counted,
6710                };
6711                (format!("count({})", counted), "integer")
6712            }
6713            "bool_and" | "bool_or" => {
6714                let Some(column) = arguments.and_then(|a| a.as_str()) else {
6715                    return Err(validation_error(format!(
6716                        "\"{}\" over \"{}\" needs the column to fold",
6717                        function, relationship.name
6718                    )));
6719                };
6720                (
6721                    format!("{}({})", function, child_scope.column(column)),
6722                    "boolean",
6723                )
6724            }
6725            other => {
6726                return Err(validation_error(format!(
6727                    "\"{}\" is not an aggregate a predicate can be written over",
6728                    other
6729                )))
6730            }
6731        };
6732
6733        let subselect = format!(
6734            "(SELECT {} FROM {} AS {} WHERE {})",
6735            aggregate,
6736            source,
6737            postrust_sql::escape_ident(&alias),
6738            correlation.join(" AND ")
6739        );
6740        let serde_json::Value::Object(operators) = predicate else {
6741            return Err(validation_error(format!(
6742                "the predicate on \"{}\" is not a comparison",
6743                function
6744            )));
6745        };
6746        for (operator, operand) in operators {
6747            conditions.push(comparison_sql(
6748                &subselect,
6749                function,
6750                Some(operand_type),
6751                operator,
6752                operand,
6753                param_idx,
6754                values,
6755            )?);
6756        }
6757    }
6758
6759    Ok(match conditions.is_empty() {
6760        true => "true".to_string(),
6761        false => format!("({})", conditions.join(" AND ")),
6762    })
6763}
6764
6765/// One spatial comparison, as a PostGIS call.
6766///
6767/// The operand arrives as GeoJSON, which is what Hasura accepts and what a
6768/// client sends, so it is parsed by `ST_GeomFromGeoJSON` rather than bound as
6769/// a shape. The cast that follows is the column's own type: the same function
6770/// takes a geometry or a geography and picking the wrong one is not an
6771/// overload PostGIS has.
6772#[allow(clippy::too_many_arguments)]
6773fn postgis_sql(
6774    quoted: &str,
6775    column_type: Option<&str>,
6776    function: &str,
6777    op: &str,
6778    operand: &serde_json::Value,
6779    param_idx: &mut usize,
6780    values: &mut Vec<serde_json::Value>,
6781) -> Result<String, async_graphql::Error> {
6782    let is_geography = column_type == Some("geography");
6783    let mut shape = |value: &serde_json::Value, param_idx: &mut usize| -> String {
6784        let placeholder = format!("${}", param_idx);
6785        *param_idx += 1;
6786        values.push(value.clone());
6787        match is_geography {
6788            true => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
6789            false => format!("ST_GeomFromGeoJSON({})", placeholder),
6790        }
6791    };
6792
6793    match op {
6794        // `{distance, from}`: the shape and how far from it.
6795        "_st_d_within" | "_st_3d_d_within" => {
6796            let serde_json::Value::Object(spec) = operand else {
6797                return Err(async_graphql::Error::new(format!(
6798                    "\"{}\" takes {{distance, from}}",
6799                    op
6800                )));
6801            };
6802            let Some(from) = spec.get("from") else {
6803                return Err(async_graphql::Error::new(format!(
6804                    "\"{}\" needs a shape to measure from",
6805                    op
6806                )));
6807            };
6808            let Some(distance) = spec.get("distance") else {
6809                return Err(async_graphql::Error::new(format!(
6810                    "\"{}\" needs a distance",
6811                    op
6812                )));
6813            };
6814            let from_sql = shape(from, param_idx);
6815            let distance_sql = format!("${}", param_idx);
6816            *param_idx += 1;
6817            values.push(distance.clone());
6818            // On a sphere the answer depends on which sphere: `ST_DWithin`
6819            // over geography measures on the spheroid by default and on a
6820            // perfect sphere when told not to, and the two disagree by enough
6821            // to change which rows come back. Geometry has no such argument.
6822            let spheroid = match (is_geography, spec.get("use_spheroid")) {
6823                (true, Some(use_spheroid)) if !use_spheroid.is_null() => {
6824                    let placeholder = format!("${}", param_idx);
6825                    *param_idx += 1;
6826                    values.push(use_spheroid.clone());
6827                    format!(", {}::boolean", placeholder)
6828                }
6829                _ => String::new(),
6830            };
6831            Ok(format!(
6832                "{}({}, {}, {}::float8{})",
6833                function, quoted, from_sql, distance_sql, spheroid
6834            ))
6835        }
6836        // A raster against another raster, bound as one rather than parsed.
6837        "_st_intersects_rast" => {
6838            let placeholder = format!("${}", param_idx);
6839            *param_idx += 1;
6840            values.push(operand.clone());
6841            Ok(format!("{}({}, {}::raster)", function, quoted, placeholder))
6842        }
6843        // A raster against a shape, in one band or in any.
6844        "_st_intersects_geom_nband" | "_st_intersects_nband_geom" => {
6845            let serde_json::Value::Object(spec) = operand else {
6846                return Err(async_graphql::Error::new(format!(
6847                    "\"{}\" takes an object",
6848                    op
6849                )));
6850            };
6851            let Some(geometry) = spec.get("geommin") else {
6852                return Err(async_graphql::Error::new(format!(
6853                    "\"{}\" needs a shape",
6854                    op
6855                )));
6856            };
6857            let band = spec.get("nband").filter(|v| !v.is_null());
6858            let geometry_sql = shape(geometry, param_idx);
6859            Ok(match band {
6860                None => format!("{}({}, {})", function, quoted, geometry_sql),
6861                Some(band) => {
6862                    let placeholder = format!("${}", param_idx);
6863                    *param_idx += 1;
6864                    values.push(band.clone());
6865                    // `ST_Intersects(raster, nband, geometry)` is the spelling
6866                    // with a band; the argument order is PostGIS's, not the
6867                    // input's.
6868                    format!(
6869                        "{}({}, {}::int, {})",
6870                        function, quoted, placeholder, geometry_sql
6871                    )
6872                }
6873            })
6874        }
6875        // Everything else is the relation between this shape and one other.
6876        _ => {
6877            let other = shape(operand, param_idx);
6878            Ok(format!("{}({}, {})", function, quoted, other))
6879        }
6880    }
6881}
6882
6883/// A list of values as an array PostgreSQL will read.
6884///
6885/// Bound one element at a time rather than as one parameter. A JSON array
6886/// arrives as the text `["a","b"]`, which is not an array literal -- PostgreSQL
6887/// answers `malformed array literal` and means it.
6888fn sql_array(
6889    items: &[serde_json::Value],
6890    cast: &str,
6891    param_idx: &mut usize,
6892    values: &mut Vec<serde_json::Value>,
6893) -> String {
6894    if items.is_empty() {
6895        return format!("ARRAY[]::{}", cast);
6896    }
6897    let placeholders: Vec<String> = items
6898        .iter()
6899        .map(|item| {
6900            let placeholder = format!("${}", param_idx);
6901            *param_idx += 1;
6902            values.push(item.clone());
6903            placeholder
6904        })
6905        .collect();
6906    format!("ARRAY[{}]::{}", placeholders.join(", "), cast)
6907}
6908
6909/// One comparison against one column.
6910#[allow(clippy::too_many_arguments)]
6911fn comparison_sql(
6912    quoted: &str,
6913    column: &str,
6914    column_type: Option<&str>,
6915    op: &str,
6916    operand: &serde_json::Value,
6917    param_idx: &mut usize,
6918    values: &mut Vec<serde_json::Value>,
6919) -> Result<String, async_graphql::Error> {
6920    // `_cast` is not a comparison but a change of what is being compared: the
6921    // column becomes another type and the comparisons inside it apply to that.
6922    // A geometry and a geography answer different questions about the same
6923    // shape -- one on a plane, one on a sphere -- and this is how a client asks
6924    // for the other without the schema carrying two columns.
6925    if op == "_cast" {
6926        let serde_json::Value::Object(casts) = operand else {
6927            return Err(async_graphql::Error::new(
6928                "_cast takes an object naming the type to compare as",
6929            ));
6930        };
6931        let mut conditions = Vec::new();
6932        for (target, comparisons) in casts {
6933            // The GraphQL name of what is being compared as, and the
6934            // PostgreSQL type behind it. `String` is the one that differs:
6935            // a document compared as text is compared as `text`.
6936            let pg_type = match target.as_str() {
6937                "geometry" | "geography" => target.as_str(),
6938                "String" => "text",
6939                _ => {
6940                    return Err(async_graphql::Error::new(format!(
6941                        "cannot compare \"{}\" as \"{}\"",
6942                        column, target
6943                    )))
6944                }
6945            };
6946            let serde_json::Value::Object(ops) = comparisons else {
6947                continue;
6948            };
6949            let cast = format!("{}::{}", quoted, pg_type);
6950            for (nested_op, nested_operand) in ops {
6951                conditions.push(comparison_sql(
6952                    &cast,
6953                    column,
6954                    Some(pg_type),
6955                    nested_op,
6956                    nested_operand,
6957                    param_idx,
6958                    values,
6959                )?);
6960            }
6961        }
6962        return Ok(match conditions.len() {
6963            0 => "true".to_string(),
6964            1 => conditions.pop().expect("just counted"),
6965            _ => format!("({})", conditions.join(" AND ")),
6966        });
6967    }
6968
6969    // A spatial relation is a function of two shapes rather than an operator
6970    // between them, so it is written before the operator table is consulted.
6971    if let Some(function) = crate::input::bool_exp::postgis_function(op) {
6972        return postgis_sql(
6973            quoted,
6974            column_type,
6975            function,
6976            op,
6977            operand,
6978            param_idx,
6979            values,
6980        );
6981    }
6982
6983    // A tree comparison is an operator, but one whose operand has to be cast:
6984    // `?` is "any of these labels" for an ltree and "has this key" for a
6985    // jsonb, and PostgreSQL tells them apart by the operand's type alone.
6986    if let Some((operator, cast)) = crate::input::bool_exp::ltree_operator(op) {
6987        if cast.ends_with("[]") {
6988            let items = operand.as_array().ok_or_else(|| {
6989                async_graphql::Error::new(format!(
6990                    "the \"{}\" comparison on \"{}\" takes a list",
6991                    op, column
6992                ))
6993            })?;
6994            return Ok(format!(
6995                "{} {} {}",
6996                quoted,
6997                operator,
6998                sql_array(items, cast, param_idx, values)
6999            ));
7000        }
7001        let placeholder = format!("${}", param_idx);
7002        *param_idx += 1;
7003        values.push(operand.clone());
7004        return Ok(format!("{} {} {}::{}", quoted, operator, placeholder, cast));
7005    }
7006
7007    // The path language is a query over the document rather than a comparison
7008    // against one, so it is a function of the column and the path: `_exists`
7009    // asks whether the path selects anything, `_match` whether the predicate
7010    // it ends in holds.
7011    if let Some(function) = match op {
7012        "_jsonb_path_exists" => Some("jsonb_path_exists"),
7013        "_jsonb_path_match" => Some("jsonb_path_match"),
7014        _ => None,
7015    } {
7016        let placeholder = format!("${}", param_idx);
7017        *param_idx += 1;
7018        values.push(operand.clone());
7019        return Ok(format!(
7020            "{}({}, {}::jsonpath)",
7021            function, quoted, placeholder
7022        ));
7023    }
7024
7025    // Comparisons binding exactly one parameter, by the SQL they become.
7026    let binary = match op {
7027        "_eq" => Some("="),
7028        "_neq" => Some("<>"),
7029        "_gt" => Some(">"),
7030        "_gte" => Some(">="),
7031        "_lt" => Some("<"),
7032        "_lte" => Some("<="),
7033        "_like" => Some("LIKE"),
7034        "_nlike" => Some("NOT LIKE"),
7035        "_ilike" => Some("ILIKE"),
7036        "_nilike" => Some("NOT ILIKE"),
7037        "_similar" => Some("SIMILAR TO"),
7038        "_nsimilar" => Some("NOT SIMILAR TO"),
7039        "_regex" => Some("~"),
7040        "_nregex" => Some("!~"),
7041        "_iregex" => Some("~*"),
7042        "_niregex" => Some("!~*"),
7043        "_contains" => Some("@>"),
7044        "_contained_in" => Some("<@"),
7045        "_has_key" => Some("?"),
7046        _ => None,
7047    };
7048
7049    if let Some(sql_op) = binary {
7050        let placeholder = format!("${}", param_idx);
7051        *param_idx += 1;
7052        // A containment operand is a whole document, and `"latest"` is one --
7053        // a JSON string, which is not the same text as `latest`. Binding the
7054        // bare string answered `invalid input syntax for type json`, so what
7055        // goes over the wire is the value as it is written in JSON.
7056        values.push(match op {
7057            "_contains" | "_contained_in" => match operand {
7058                serde_json::Value::String(_)
7059                | serde_json::Value::Number(_)
7060                | serde_json::Value::Bool(_) => serde_json::Value::String(operand.to_string()),
7061                other => other.clone(),
7062            },
7063            _ => operand.clone(),
7064        });
7065        // A bound parameter arrives as text and PostgreSQL infers a type for
7066        // it from the operator, which works only while the inference is
7067        // unambiguous. `jsonb @> text` is not an operator at all; and `id =
7068        // $1` against an integer column answers `operator does not exist:
7069        // integer = text` the moment the value is one PostgreSQL cannot read
7070        // as a number -- a null, most obviously, which is how a client asks a
7071        // question it expects an empty answer to.
7072        //
7073        // So the operand says what it is. The containment and key operators
7074        // name the type the operator needs; everything else names the
7075        // column's, which is the type it is being compared against.
7076        let cast = match op {
7077            "_contains" | "_contained_in" => "::jsonb".to_string(),
7078            "_has_key" => "::text".to_string(),
7079            // Only where inference would otherwise pick text and be wrong. A
7080            // cast tells PostgreSQL what the *result* is, and it infers the
7081            // parameter feeding it as text -- so `$1::int4` bound with a
7082            // number sends binary int8 bytes into a text parameter and
7083            // answers `invalid byte sequence for encoding "UTF8"`. A value
7084            // PostgreSQL can read as a number needs no help; a null is the one
7085            // that does, because `id = $1` with nothing to infer from is
7086            // `integer = text`.
7087            _ if operand.is_null() => match column_type {
7088                Some(pg_type) => format!("::{}", pg_type),
7089                None => String::new(),
7090            },
7091            // A parameter arrives as `text`, and PostgreSQL then resolves the
7092            // operator by taking the *column* down to text where it can --
7093            // which is the wrong operator, and sometimes no operator at all.
7094            // `citext = text` compares case-sensitively against a
7095            // case-insensitive column; `uuid = text` does not exist. So a
7096            // written value names the type it is being compared against.
7097            //
7098            // Only a string: a number bound with a cast is sent as binary into
7099            // a parameter PostgreSQL has inferred as text, which is the
7100            // `invalid byte sequence for encoding "UTF8"` this used to answer.
7101            _ if operand.is_string() => match column_type {
7102                Some(pg_type) => format!("::{}", pg_type),
7103                None => String::new(),
7104            },
7105            _ => String::new(),
7106        };
7107        return Ok(format!("{} {} {}{}", quoted, sql_op, placeholder, cast));
7108    }
7109
7110    match op {
7111        "_is_null" => Ok(if operand.as_bool().unwrap_or(false) {
7112            format!("{} IS NULL", quoted)
7113        } else {
7114            format!("{} IS NOT NULL", quoted)
7115        }),
7116        "_in" | "_nin" => {
7117            let items = operand.as_array().ok_or_else(|| {
7118                async_graphql::Error::new(format!(
7119                    "the \"{}\" comparison on \"{}\" requires a list of values",
7120                    op, column
7121                ))
7122            })?;
7123            if items.is_empty() {
7124                // `IN ()` is not valid SQL. An empty `_in` matches nothing and
7125                // an empty `_nin` matches everything.
7126                return Ok(if op == "_in" { "false" } else { "true" }.to_string());
7127            }
7128            let mut placeholders = Vec::with_capacity(items.len());
7129            for item in items {
7130                // Cast only a null, for the reason the binary comparisons
7131                // give: a cast makes PostgreSQL infer the parameter as text.
7132                let cast = match (item.is_null() || item.is_string(), column_type) {
7133                    (true, Some(pg_type)) => format!("::{}", pg_type),
7134                    _ => String::new(),
7135                };
7136                placeholders.push(format!("${}{}", param_idx, cast));
7137                values.push(item.clone());
7138                *param_idx += 1;
7139            }
7140            Ok(format!(
7141                "{} {} ({})",
7142                quoted,
7143                if op == "_in" { "IN" } else { "NOT IN" },
7144                placeholders.join(", ")
7145            ))
7146        }
7147        "_has_keys_any" | "_has_keys_all" => {
7148            let items = operand.as_array().ok_or_else(|| {
7149                async_graphql::Error::new(format!(
7150                    "the \"{}\" comparison on \"{}\" requires a list of keys",
7151                    op, column
7152                ))
7153            })?;
7154            Ok(format!(
7155                "{} {} {}",
7156                quoted,
7157                if op == "_has_keys_any" { "?|" } else { "?&" },
7158                sql_array(items, "text[]", param_idx, values)
7159            ))
7160        }
7161        other => Err(async_graphql::Error::new(format!(
7162            "unsupported comparison \"{}\" on \"{}\"",
7163            other, column
7164        ))),
7165    }
7166}
7167
7168/// Build an `ORDER BY` clause from the `order_by` argument.
7169///
7170/// The argument is a list of single-column objects -- `[{name: asc}, {id:
7171/// desc}]` -- and the list is ordered, which is why it is a list and not one
7172/// object with several keys. Column names are checked against the table in the
7173/// schema cache before being quoted, so a name that is unknown, or crafted to
7174/// inject SQL, is rejected rather than interpolated. Returns an empty string
7175/// when no ordering was requested.
7176#[allow(clippy::too_many_arguments)] // one parameter per SQL clause
7177async fn build_order_by_clause(
7178    ctx: &ResolverContext<'_>,
7179    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
7180    schema_name: &str,
7181    table_name: &str,
7182    type_name: &str,
7183    relationships: &HashMap<String, Vec<RelationshipField>>,
7184    reference: &str,
7185    names: &crate::names::NameOverrides,
7186) -> Result<String, async_graphql::Error> {
7187    let Ok(order_arg) = ctx.args.try_get("order_by") else {
7188        return Ok(String::new());
7189    };
7190
7191    let value = accessor_to_json(&order_arg);
7192    // A single object is accepted as a one-entry list, which is what a client
7193    // writing `order_by: {name: asc}` means and what Hasura reads it as.
7194    let entries: Vec<&serde_json::Value> = match &value {
7195        serde_json::Value::Array(items) => items.iter().collect(),
7196        serde_json::Value::Object(_) => vec![&value],
7197        serde_json::Value::Null => return Ok(String::new()),
7198        _ => {
7199            return Err(async_graphql::Error::new(
7200                "order_by takes an object or a list of them",
7201            ))
7202        }
7203    };
7204    if entries.is_empty() {
7205        return Ok(String::new());
7206    }
7207
7208    let guard = schema_cache
7209        .get()
7210        .await
7211        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7212    let cache = guard
7213        .as_ref()
7214        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
7215    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
7216    let table = cache
7217        .get_table(&qi)
7218        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
7219
7220    let mut terms = Vec::new();
7221    let mut alias_counter = 0usize;
7222    for entry in entries {
7223        order_terms_into(
7224            entry,
7225            cache,
7226            relationships,
7227            type_name,
7228            table,
7229            reference,
7230            names,
7231            &mut alias_counter,
7232            &mut terms,
7233        )?;
7234    }
7235
7236    if terms.is_empty() {
7237        return Ok(String::new());
7238    }
7239    Ok(format!(" ORDER BY {}", terms.join(", ")))
7240}
7241
7242/// Collect the `ORDER BY` terms one entry contributes.
7243///
7244/// A key whose value is a direction is a column of this table. A key whose
7245/// value is an object is something the row points at, and ordering by it is a
7246/// correlated subselect: one related row contributes its column, and many
7247/// contribute an aggregate. That is the whole difference between the two
7248/// sides, and PostgreSQL will take a scalar subquery anywhere a column goes.
7249#[allow(clippy::too_many_arguments)]
7250fn order_terms_into(
7251    entry: &serde_json::Value,
7252    cache: &SchemaCache,
7253    relationships: &HashMap<String, Vec<RelationshipField>>,
7254    type_name: &str,
7255    table: &postrust_core::schema_cache::Table,
7256    reference: &str,
7257    names: &crate::names::NameOverrides,
7258    alias_counter: &mut usize,
7259    terms: &mut Vec<String>,
7260) -> Result<(), async_graphql::Error> {
7261    let serde_json::Value::Object(map) = entry else {
7262        return Err(async_graphql::Error::new(
7263            "each order_by entry is an object mapping a column to a direction",
7264        ));
7265    };
7266    let available: &[RelationshipField] = relationships
7267        .get(type_name)
7268        .map(|r| r.as_slice())
7269        .unwrap_or(&[]);
7270
7271    for (key, value) in map {
7272        // A direction given as null is no direction. `order_by: {id:
7273        // $direction}` with nothing bound for `$direction` is how a client
7274        // makes an ordering optional without writing the query twice, and the
7275        // same goes for `{author: $author_order_by}`, which is a whole
7276        // ordering left out.
7277        if value.is_null() {
7278            continue;
7279        }
7280
7281        // A direction: this table's own column, or a function of its row.
7282        if let Some(name) = value.as_str() {
7283            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
7284                async_graphql::Error::new(format!(
7285                    "\"{}\" is not a sort direction; expected one of asc, desc, \
7286                     asc_nulls_first, asc_nulls_last, desc_nulls_first, desc_nulls_last",
7287                    name
7288                ))
7289            })?;
7290            let column = table_column_for(names, table, key);
7291            if table.get_column(column).is_some() {
7292                terms.push(format!(
7293                    "{}.{} {}",
7294                    reference,
7295                    postrust_sql::escape_ident(column),
7296                    sql
7297                ));
7298                continue;
7299            }
7300            // A computed field is a column as far as ordering is concerned:
7301            // the row is the argument, and the call is what is sorted on.
7302            let function = names
7303                .computed_source(&table.schema, &table.name, key)
7304                .unwrap_or(key);
7305            if let Some(definition) = table.get_computed_column(function) {
7306                terms.push(format!(
7307                    "{} {}",
7308                    computed_call(definition, &format!("{}.*", reference), reference),
7309                    sql
7310                ));
7311                continue;
7312            }
7313            return Err(async_graphql::Error::new(format!(
7314                "cannot order by unknown column \"{}\" on \"{}\"",
7315                key, table.name
7316            )));
7317        }
7318
7319        // An aggregate of the rows that point here.
7320        if let Some(rel) = key
7321            .strip_suffix("_aggregate")
7322            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list))
7323        {
7324            aggregate_order_terms(value, rel, cache, reference, alias_counter, terms)?;
7325            continue;
7326        }
7327
7328        // A column of the row this one points at.
7329        if let Some(rel) = available.iter().find(|r| r.name == *key && !r.is_list) {
7330            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
7331                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7332            if plan.columns.is_empty() {
7333                return Err(async_graphql::Error::new(format!(
7334                    "cannot order by \"{}\": it is not reached by a key",
7335                    key
7336                )));
7337            }
7338            *alias_counter += 1;
7339            let alias = format!("pgrst_ord_{}", alias_counter);
7340            let quoted_alias = postrust_sql::escape_ident(&alias);
7341            let correlation = plan
7342                .columns
7343                .iter()
7344                .map(|(local, foreign)| {
7345                    format!(
7346                        "{} = {}.{}",
7347                        format_args!("{}.{}", reference, postrust_sql::escape_ident(local)),
7348                        quoted_alias,
7349                        postrust_sql::escape_ident(foreign)
7350                    )
7351                })
7352                .collect::<Vec<_>>()
7353                .join(" AND ");
7354
7355            let target_qi = postrust_core::api_request::QualifiedIdentifier::new(
7356                &plan.foreign_schema,
7357                &plan.foreign_table,
7358            );
7359            let Some(target) = cache.get_table(&target_qi) else {
7360                return Err(async_graphql::Error::new(format!(
7361                    "unknown table \"{}\"",
7362                    plan.foreign_table
7363                )));
7364            };
7365
7366            // The related row's own terms, then wrapped one at a time: a
7367            // subquery yields one value, so each term is its own subselect.
7368            let mut nested = Vec::new();
7369            order_terms_into(
7370                value,
7371                cache,
7372                relationships,
7373                &rel.target_type,
7374                target,
7375                &quoted_alias,
7376                names,
7377                alias_counter,
7378                &mut nested,
7379            )?;
7380            for term in nested {
7381                let (expression, direction) = term
7382                    .rsplit_once(' ')
7383                    .map(|(e, d)| (e.to_string(), d.to_string()))
7384                    .unwrap_or((term.clone(), String::new()));
7385                terms.push(format!(
7386                    "(SELECT {} FROM {}.{} AS {} WHERE {}) {}",
7387                    expression,
7388                    postrust_sql::escape_ident(&plan.foreign_schema),
7389                    postrust_sql::escape_ident(&plan.foreign_table),
7390                    quoted_alias,
7391                    correlation,
7392                    direction
7393                ));
7394            }
7395            continue;
7396        }
7397
7398        return Err(async_graphql::Error::new(format!(
7399            "cannot order by \"{}\" on \"{}\"",
7400            key, table.name
7401        )));
7402    }
7403    Ok(())
7404}
7405
7406/// The `ORDER BY` terms for an aggregate of a row's children.
7407fn aggregate_order_terms(
7408    value: &serde_json::Value,
7409    rel: &RelationshipField,
7410    cache: &SchemaCache,
7411    reference: &str,
7412    alias_counter: &mut usize,
7413    terms: &mut Vec<String>,
7414) -> Result<(), async_graphql::Error> {
7415    let serde_json::Value::Object(spec) = value else {
7416        return Err(async_graphql::Error::new(
7417            "ordering by an aggregate takes an object, such as {count: desc}",
7418        ));
7419    };
7420    let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
7421        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7422    if plan.columns.is_empty() && plan.function.is_none() {
7423        return Err(async_graphql::Error::new(format!(
7424            "cannot order by an aggregate of \"{}\": it is not reached by a key",
7425            rel.name
7426        )));
7427    }
7428    // A computed relationship whose function takes more than the row cannot be
7429    // ordered by here: there is nowhere in `order_by` to write the arguments.
7430    if plan.function.is_some() && !rel.arguments.is_empty() {
7431        return Err(async_graphql::Error::new(format!(
7432            "cannot order by an aggregate of \"{}\": it takes arguments, and an \
7433             ordering has nowhere to write them",
7434            rel.name
7435        )));
7436    }
7437
7438    *alias_counter += 1;
7439    let alias = postrust_sql::escape_ident(&format!("pgrst_ord_{}", alias_counter));
7440    // A computed relationship is correlated by argument -- the function takes
7441    // the parent row -- where a key relationship is correlated by columns.
7442    let (source, correlation) = match &plan.function {
7443        Some(function) => {
7444            let session = plan.row_argument.as_ref().and_then(|row_argument| {
7445                computed_session_argument(cache, function, row_argument)
7446                    .map(|session| (row_argument.clone(), session))
7447            });
7448            let call = match session {
7449                Some((row_argument, session)) => format!(
7450                    "{} => {}, {} => {}",
7451                    postrust_sql::escape_ident(&row_argument),
7452                    reference,
7453                    postrust_sql::escape_ident(&session),
7454                    SESSION_ARGUMENT
7455                ),
7456                None => reference.to_string(),
7457            };
7458            (
7459                format!(
7460                    "{}.{}({})",
7461                    postrust_sql::escape_ident(&function.schema),
7462                    postrust_sql::escape_ident(&function.name),
7463                    call
7464                ),
7465                "true".to_string(),
7466            )
7467        }
7468        None => (
7469            format!(
7470                "{}.{}",
7471                postrust_sql::escape_ident(&plan.foreign_schema),
7472                postrust_sql::escape_ident(&plan.foreign_table)
7473            ),
7474            plan.columns
7475                .iter()
7476                .map(|(local, foreign)| {
7477                    format!(
7478                        "{}.{} = {}.{}",
7479                        reference,
7480                        postrust_sql::escape_ident(local),
7481                        alias,
7482                        postrust_sql::escape_ident(foreign)
7483                    )
7484                })
7485                .collect::<Vec<_>>()
7486                .join(" AND "),
7487        ),
7488    };
7489
7490    // `{count: desc}` is one term; `{max: {id: desc}}` is one per column.
7491    let mut wanted: Vec<(String, String)> = Vec::new();
7492    for (function, argument) in spec {
7493        // A direction given as null is no direction, as everywhere else an
7494        // ordering is read.
7495        if argument.is_null() {
7496            continue;
7497        }
7498        if function == "count" {
7499            let name = argument.as_str().unwrap_or_default();
7500            let Some(direction) = crate::input::order_by::direction_sql(name) else {
7501                return Err(async_graphql::Error::new(format!(
7502                    "\"{}\" is not a sort direction",
7503                    name
7504                )));
7505            };
7506            wanted.push(("count(*)".to_string(), direction.to_string()));
7507            continue;
7508        }
7509        let serde_json::Value::Object(columns) = argument else {
7510            continue;
7511        };
7512        for (column, direction) in columns {
7513            if direction.is_null() {
7514                continue;
7515            }
7516            let name = direction.as_str().unwrap_or_default();
7517            let Some(direction) = crate::input::order_by::direction_sql(name) else {
7518                return Err(async_graphql::Error::new(format!(
7519                    "\"{}\" is not a sort direction",
7520                    name
7521                )));
7522            };
7523            // The function comes from the generated input, not from the
7524            // request: a client can only name one that was offered.
7525            wanted.push((
7526                format!(
7527                    "{}({}.{})",
7528                    function,
7529                    alias,
7530                    postrust_sql::escape_ident(column)
7531                ),
7532                direction.to_string(),
7533            ));
7534        }
7535    }
7536
7537    for (expression, direction) in wanted {
7538        terms.push(format!(
7539            "(SELECT {} FROM {} AS {} WHERE {}) {}",
7540            expression, source, alias, correlation, direction
7541        ));
7542    }
7543    Ok(())
7544}
7545
7546/// The `DISTINCT ON (...)` prefix, and the ordering that has to go with it.
7547///
7548/// PostgreSQL keeps the first row of each DISTINCT ON group in the query's own
7549/// order, and picks arbitrarily where the ordering does not begin with the
7550/// distinct columns -- so which row survives would depend on the plan. Hasura
7551/// refuses that query rather than answering it, and so does this: prepending
7552/// the distinct columns instead produced an answer, and a wrong one, since
7553/// `ORDER BY "department", "department" DESC` is decided by its first term and
7554/// sorts ascending. Where nothing was ordered at all there is nothing to
7555/// disagree with, so the distinct columns become the ordering.
7556///
7557/// `written` is the ordering as its comma-separated terms, without the
7558/// keyword; the answer is in the same spelling.
7559fn distinct_on_clause(
7560    distinct_on: &[String],
7561    written: Option<&str>,
7562) -> Result<(String, Option<String>), async_graphql::Error> {
7563    if distinct_on.is_empty() {
7564        return Ok((String::new(), written.map(str::to_string)));
7565    }
7566    let terms: Vec<&str> = written
7567        .map(|written| written.split(", ").collect())
7568        .unwrap_or_default();
7569    // Terms are qualified and the distinct columns may not be, so they are
7570    // compared by the name itself.
7571    let column_of = |term: &str| {
7572        let expression = term.split_whitespace().next().unwrap_or(term);
7573        expression
7574            .rsplit_once('.')
7575            .map(|(_, name)| name)
7576            .unwrap_or(expression)
7577            .to_string()
7578    };
7579    let leading: Vec<String> = terms
7580        .iter()
7581        .take(distinct_on.len())
7582        .map(|term| column_of(term))
7583        .collect();
7584    if !terms.is_empty()
7585        && (leading.len() != distinct_on.len()
7586            || distinct_on
7587                .iter()
7588                .any(|column| !leading.contains(&column_of(column))))
7589    {
7590        return Err(validation_error(
7591            "\"distinct_on\" columns must match initial \"order_by\" columns",
7592        ));
7593    }
7594    let order = match terms.is_empty() {
7595        true => Some(distinct_on.join(", ")),
7596        false => written.map(str::to_string),
7597    };
7598    Ok((format!("DISTINCT ON ({}) ", distinct_on.join(", ")), order))
7599}
7600
7601/// Build the `DISTINCT ON (...)` prefix from the `distinct_on` argument.
7602///
7603/// PostgreSQL requires the leftmost `ORDER BY` terms to match the distinct
7604/// columns, and picks an arbitrary surviving row otherwise. The caller
7605/// prepends these terms to whatever ordering was asked for rather than
7606/// replacing it, so `distinct_on: [name]` with `order_by: [{id: desc}]` keeps
7607/// the highest id per name instead of an unpredictable one.
7608async fn build_distinct_on(
7609    ctx: &ResolverContext<'_>,
7610    schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
7611    schema_name: &str,
7612    table_name: &str,
7613    names: &crate::names::NameOverrides,
7614) -> Result<Vec<String>, async_graphql::Error> {
7615    let Ok(arg) = ctx.args.try_get("distinct_on") else {
7616        return Ok(Vec::new());
7617    };
7618    let value = accessor_to_json(&arg);
7619    let requested: Vec<String> = match &value {
7620        serde_json::Value::Array(items) => items
7621            .iter()
7622            .filter_map(|i| i.as_str().map(|s| s.to_string()))
7623            .collect(),
7624        serde_json::Value::String(one) => vec![one.clone()],
7625        _ => Vec::new(),
7626    };
7627    if requested.is_empty() {
7628        return Ok(Vec::new());
7629    }
7630
7631    let guard = schema_cache
7632        .get()
7633        .await
7634        .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7635    let cache = guard
7636        .as_ref()
7637        .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
7638    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
7639    let table = cache
7640        .get_table(&qi)
7641        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
7642
7643    let mut quoted = Vec::with_capacity(requested.len());
7644    for field in requested {
7645        let name = table_column_for(names, table, &field).to_string();
7646        if table.get_column(&name).is_none() {
7647            return Err(async_graphql::Error::new(format!(
7648                "cannot take distinct on unknown column \"{}\" of \"{}\"",
7649                field, table_name
7650            )));
7651        }
7652        quoted.push(postrust_sql::escape_ident(&name));
7653    }
7654    Ok(quoted)
7655}
7656
7657/// Build the SELECT-list expressions that embed relationships in one query.
7658///
7659/// The GraphQL mirror of the REST builder: each requested relationship becomes a
7660/// correlated subselect yielding JSON, so the whole selection comes back from
7661/// the parent query instead of one query per relationship per level.
7662/// The SELECT-list entries for any computed columns the selection asks for.
7663///
7664/// A computed column is a function of one argument of the table's own row
7665/// type, so it is not in `*` and has to be named. PostgreSQL's functional
7666/// notation reads `upper_name(author.*)` and `author.upper_name` as the same
7667/// call; the explicit form is written here because it says which function is
7668/// being called.
7669#[allow(clippy::too_many_arguments)] // the selection, its source, and the binding state
7670fn computed_projections(
7671    table: &postrust_core::schema_cache::Table,
7672    selection: async_graphql::SelectionField<'_>,
7673    row_reference: &str,
7674    names: &crate::names::NameOverrides,
7675    schema_cache: &SchemaCache,
7676    param_idx: &mut usize,
7677    values: &mut Vec<serde_json::Value>,
7678) -> Result<Vec<String>, async_graphql::Error> {
7679    let mut projections = Vec::new();
7680    for field in selection.selection_set() {
7681        let name = field.name();
7682        // A real column wins, and the projection already carries it -- under
7683        // this name, whether or not that is the column's own.
7684        if table
7685            .get_column(table_column_for(names, table, name))
7686            .is_some()
7687        {
7688            continue;
7689        }
7690        // The field may be exposed under a name that was given rather than
7691        // the function's own, so the call is looked up from either side.
7692        let function = names
7693            .computed_source(&table.schema, &table.name, name)
7694            .unwrap_or(name);
7695        let Some(computed) = table.get_computed_column(function) else {
7696            continue;
7697        };
7698        let arguments = crate::schema::computed_caller_arguments(computed, schema_cache);
7699        let given = match arguments.is_empty() {
7700            true => None,
7701            false => embed_arguments(field).remove("args"),
7702        };
7703        projections.push(format!(
7704            "{} AS {}",
7705            computed_column_call(
7706                computed,
7707                row_reference,
7708                name,
7709                &arguments,
7710                given.as_ref(),
7711                param_idx,
7712                values,
7713            )?,
7714            postrust_sql::escape_ident(name)
7715        ));
7716    }
7717    Ok(projections)
7718}
7719
7720/// The call behind a computed field, including whatever the caller passed.
7721///
7722/// The ordinary case is a function of the row alone, which is written
7723/// positionally: that is the call REST writes, and the one a client that never
7724/// heard of `args` is asking for. Anything else has to be written by name --
7725/// the row is then not the only parameter and may not be the first -- so the
7726/// row, the session where the function asks for one, and each argument the
7727/// caller gave are all named.
7728fn computed_column_call(
7729    computed: &postrust_core::schema_cache::ComputedColumn,
7730    row: &str,
7731    field_name: &str,
7732    arguments: &[(String, String, bool)],
7733    given: Option<&serde_json::Value>,
7734    param_idx: &mut usize,
7735    values: &mut Vec<serde_json::Value>,
7736) -> Result<String, async_graphql::Error> {
7737    if arguments.is_empty() {
7738        return Ok(computed_call(computed, row, row));
7739    }
7740    let Some(row_argument) = &computed.row_argument else {
7741        return Ok(computed_call(computed, row, row));
7742    };
7743    let function = format!(
7744        "{}.{}",
7745        postrust_sql::escape_ident(&computed.function.schema),
7746        postrust_sql::escape_ident(&computed.function.name)
7747    );
7748    let mut passed = vec![format!(
7749        "{} => {}",
7750        postrust_sql::escape_ident(row_argument),
7751        row
7752    )];
7753    if let Some(session) = &computed.session_argument {
7754        passed.push(format!(
7755            "{} => {}",
7756            postrust_sql::escape_ident(session),
7757            SESSION_ARGUMENT
7758        ));
7759    }
7760    for (name, pg_type, required) in arguments {
7761        match given.and_then(|given| given.get(name)) {
7762            None if *required => {
7763                return Err(async_graphql::Error::new(format!(
7764                    "{} needs the argument \"{}\"",
7765                    field_name, name
7766                )))
7767            }
7768            // Left out is left out, so the function's own default applies.
7769            None => continue,
7770            Some(value) => {
7771                passed.push(format!(
7772                    "{} => ${}::{}",
7773                    postrust_sql::escape_ident(name),
7774                    param_idx,
7775                    pg_type
7776                ));
7777                *param_idx += 1;
7778                values.push(value.clone());
7779            }
7780        }
7781    }
7782    Ok(format!("{}({})", function, passed.join(", ")))
7783}
7784
7785/// The `count(...)` a selection asked for.
7786///
7787/// `count` alone is `count(*)`, which counts rows. `count(columns: [a])` is
7788/// `count("a")`, which counts the rows where `a` is not null -- a different
7789/// number, and the one the client asked for. `distinct: true` counts the
7790/// distinct values among them, and several columns are counted as the row
7791/// they make, which is how PostgreSQL counts more than one thing at once.
7792///
7793/// The column names come from the generated enum, so a selection can only
7794/// name a column the table has; what it may not know is the column's own name
7795/// where the schema exposes it under another.
7796fn count_expression(
7797    field: async_graphql::SelectionField<'_>,
7798    table: (&str, &str),
7799    alias: Option<&str>,
7800    names: &crate::names::NameOverrides,
7801) -> String {
7802    let arguments = embed_arguments(field);
7803    let requested = match arguments.get("columns") {
7804        Some(serde_json::Value::Array(items)) => items
7805            .iter()
7806            .filter_map(|item| item.as_str())
7807            .collect::<Vec<_>>(),
7808        Some(serde_json::Value::String(one)) => vec![one.as_str()],
7809        _ => Vec::new(),
7810    };
7811    if requested.is_empty() {
7812        return "count(*)".to_string();
7813    }
7814    let columns: Vec<String> = requested
7815        .into_iter()
7816        .map(|column| {
7817            let (schema, name) = table;
7818            let source = column_for(names, schema, name, column);
7819            match alias {
7820                Some(alias) => format!(
7821                    "{}.{}",
7822                    postrust_sql::escape_ident(alias),
7823                    postrust_sql::escape_ident(source)
7824                ),
7825                None => postrust_sql::escape_ident(source),
7826            }
7827        })
7828        .collect();
7829    // One column counts itself; several count the row they make, since
7830    // `count(a, b)` is not a call PostgreSQL has.
7831    let counted = match columns.len() {
7832        1 => columns.into_iter().next().unwrap_or_default(),
7833        _ => format!("({})", columns.join(", ")),
7834    };
7835    let distinct = matches!(
7836        arguments.get("distinct"),
7837        Some(serde_json::Value::Bool(true))
7838    );
7839    match distinct {
7840        true => format!("count(DISTINCT {})", counted),
7841        false => format!("count({})", counted),
7842    }
7843}
7844
7845/// The SELECT list for a nested aggregate.
7846///
7847/// `articles_aggregate { aggregate { count } nodes { title } }` becomes one
7848/// row per parent, correlated the way any embed is. Both halves are aggregates
7849/// over the same correlated set, so they go in one select list rather than two
7850/// queries -- `count(*)` and `json_agg(...)` read the same rows.
7851#[allow(clippy::too_many_arguments)] // the recursion carries its whole context
7852fn nested_aggregate_select(
7853    caller: &crate::role::Caller<'_>,
7854    selection: async_graphql::SelectionField<'_>,
7855    child_alias: &str,
7856    schema_cache: &SchemaCache,
7857    relationships: &HashMap<String, Vec<RelationshipField>>,
7858    target_type: &str,
7859    child_table: Option<&postrust_core::schema_cache::Table>,
7860    max_rows: Option<i64>,
7861    alias_counter: &mut usize,
7862    param_idx: &mut usize,
7863    values: &mut Vec<serde_json::Value>,
7864    names: &crate::names::NameOverrides,
7865) -> Result<String, async_graphql::Error> {
7866    use crate::schema::aggregate as agg;
7867
7868    let mut parts: Vec<String> = Vec::new();
7869
7870    for child in selection.selection_set() {
7871        match child.name() {
7872            "aggregate" => {
7873                let of_table = child_table
7874                    .map(|table| (table.schema.as_str(), table.name.as_str()))
7875                    .unwrap_or(("", ""));
7876                // Each `count` is answered under the name it was asked for,
7877                // since two of them may count different things. Every other
7878                // aggregate is one object read under however many names, so
7879                // its columns are the union of what all of them named.
7880                let mut counts: Vec<(String, String)> = Vec::new();
7881                let mut wanted: BTreeMap<&str, Vec<String>> = BTreeMap::new();
7882                for function in child.selection_set() {
7883                    if function.name() == "count" {
7884                        counts.push((
7885                            function.alias().unwrap_or("count").to_string(),
7886                            count_expression(function, of_table, Some(child_alias), names),
7887                        ));
7888                        continue;
7889                    }
7890                    let sql_function = agg::NUMERIC_AGGREGATES
7891                        .iter()
7892                        .chain(agg::ORDERED_AGGREGATES.iter())
7893                        .find(|(name, _)| *name == function.name())
7894                        .map(|(name, _)| *name);
7895                    let Some(sql_function) = sql_function else {
7896                        continue;
7897                    };
7898                    let columns = wanted.entry(sql_function).or_default();
7899                    for column in function.selection_set() {
7900                        let name = column.name().to_string();
7901                        if !columns.contains(&name) {
7902                            columns.push(name);
7903                        }
7904                    }
7905                }
7906                // Answered whether or not it was asked for, for the reason
7907                // the root aggregate answers it.
7908                let mut fields = match counts.iter().any(|(key, _)| key == "count") {
7909                    true => Vec::new(),
7910                    false => vec!["'count', count(*)".to_string()],
7911                };
7912                for (key, expression) in &counts {
7913                    fields.push(format!("'{}', {}", key.replace('\'', "''"), expression));
7914                }
7915                for (sql_function, named) in &wanted {
7916                    let columns: Vec<String> = named
7917                        .iter()
7918                        .map(|column| {
7919                            let source = child_table
7920                                .map(|table| table_column_for(names, table, column))
7921                                .unwrap_or(column.as_str());
7922                            format!(
7923                                "'{}', {}({}.{})",
7924                                column.replace('\'', "''"),
7925                                sql_function,
7926                                postrust_sql::escape_ident(child_alias),
7927                                postrust_sql::escape_ident(source)
7928                            )
7929                        })
7930                        .collect();
7931                    if !columns.is_empty() {
7932                        fields.push(format!(
7933                            "'{}', json_build_object({})",
7934                            sql_function,
7935                            columns.join(", ")
7936                        ));
7937                    }
7938                }
7939                parts.push(format!(
7940                    "json_build_object({}) AS {}",
7941                    fields.join(", "),
7942                    postrust_sql::escape_ident("aggregate")
7943                ));
7944            }
7945            // `nodes` under an aggregate is a rows selection like any other:
7946            // what it names may be a column, a computed field, or a
7947            // relationship of its own, and the last of those is a further
7948            // correlated subselect rather than a column of this one.
7949            "nodes" => {
7950                let embeds = build_embed_expressions(
7951                    caller,
7952                    schema_cache,
7953                    relationships,
7954                    target_type,
7955                    child_alias,
7956                    child,
7957                    max_rows,
7958                    alias_counter,
7959                    param_idx,
7960                    values,
7961                    names,
7962                )?;
7963                let mut columns: Vec<String> = Vec::new();
7964                for column in child.selection_set() {
7965                    let name = column.name();
7966                    if name == "__typename" || embeds.iter().any(|(field, _)| field == name) {
7967                        continue;
7968                    }
7969                    columns.push(format!(
7970                        "'{}', {}",
7971                        name.replace('\'', "''"),
7972                        column_expression(child_table, child_alias, name, names)
7973                    ));
7974                }
7975                for (field, expression) in &embeds {
7976                    columns.push(format!("'{}', {}", field.replace('\'', "''"), expression));
7977                }
7978                let row = if columns.is_empty() {
7979                    format!("row_to_json({})", postrust_sql::escape_ident(child_alias))
7980                } else {
7981                    format!("json_build_object({})", columns.join(", "))
7982                };
7983                parts.push(format!(
7984                    "COALESCE(json_agg({}), '[]'::json) AS {}",
7985                    row,
7986                    postrust_sql::escape_ident("nodes")
7987                ));
7988            }
7989            _ => {}
7990        }
7991    }
7992
7993    if parts.is_empty() {
7994        // A selection of nothing but `__typename`. One row, no columns, is not
7995        // valid SQL; a count nobody reads is.
7996        parts.push("count(*) AS pgrst_empty".to_string());
7997    }
7998    Ok(parts.join(", "))
7999}
8000
8001/// How one named field of a row is read, given the alias the row goes by.
8002///
8003/// A column is itself; a computed field is the call that produces it. Which of
8004/// the two a name is depends on the table, so this is the one place that asks.
8005fn column_expression(
8006    table: Option<&postrust_core::schema_cache::Table>,
8007    alias: &str,
8008    name: &str,
8009    names: &crate::names::NameOverrides,
8010) -> String {
8011    let column = table
8012        .map(|t| table_column_for(names, t, name))
8013        .unwrap_or(name);
8014    let plain = format!(
8015        "{}.{}",
8016        postrust_sql::escape_ident(alias),
8017        postrust_sql::escape_ident(column)
8018    );
8019    let computed = table
8020        .filter(|t| t.get_column(column).is_none())
8021        .and_then(|t| {
8022            let function = names
8023                .computed_source(&t.schema, &t.name, name)
8024                .unwrap_or(name);
8025            t.get_computed_column(function)
8026        });
8027    match computed {
8028        Some(definition) => computed_call(
8029            definition,
8030            &format!("{}.*", postrust_sql::escape_ident(alias)),
8031            &postrust_sql::escape_ident(alias),
8032        ),
8033        None => plain,
8034    }
8035}
8036
8037/// Render `order_by` terms against a table, qualified with an alias.
8038///
8039/// The root field's ordering reads its argument from the resolver context and
8040/// needs the schema cache asynchronously; an embed already holds both, so this
8041/// takes the value directly. Columns are checked against the table before they
8042/// are quoted, so an unknown or crafted name is refused rather than
8043/// interpolated.
8044fn order_terms(
8045    order: &serde_json::Value,
8046    schema_cache: &SchemaCache,
8047    schema_name: &str,
8048    table_name: &str,
8049    alias: &str,
8050    names: &crate::names::NameOverrides,
8051) -> Result<Option<String>, async_graphql::Error> {
8052    let entries: Vec<&serde_json::Value> = match order {
8053        serde_json::Value::Array(items) => items.iter().collect(),
8054        serde_json::Value::Object(_) => vec![order],
8055        _ => return Ok(None),
8056    };
8057
8058    let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8059    let table = schema_cache
8060        .get_table(&qi)
8061        .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8062
8063    let mut terms = Vec::new();
8064    for entry in entries {
8065        let serde_json::Value::Object(map) = entry else {
8066            continue;
8067        };
8068        for (field, direction) in map {
8069            // A direction given as null is no direction, as at the root.
8070            if direction.is_null() {
8071                continue;
8072            }
8073            let column = table_column_for(names, table, field);
8074            if table.get_column(column).is_none() {
8075                return Err(async_graphql::Error::new(format!(
8076                    "cannot order by unknown column \"{}\" on \"{}\"",
8077                    field, table_name
8078                )));
8079            }
8080            let name = direction.as_str().unwrap_or_default();
8081            let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8082                async_graphql::Error::new(format!("\"{}\" is not a sort direction", name))
8083            })?;
8084            terms.push(format!(
8085                "{}.{} {}",
8086                postrust_sql::escape_ident(alias),
8087                postrust_sql::escape_ident(column),
8088                sql
8089            ));
8090        }
8091    }
8092
8093    Ok(if terms.is_empty() {
8094        None
8095    } else {
8096        Some(terms.join(", "))
8097    })
8098}
8099
8100/// Whether a to-one relationship always has a row at the other end.
8101///
8102/// It does when this row's own key columns cannot be null: a foreign key
8103/// guarantees a matching row for every value, so a `NOT NULL` key is a
8104/// relationship that cannot be absent, and the field is non-null. A client
8105/// generating types then gets `author` rather than `author | null`, which is
8106/// what the schema actually promises.
8107///
8108/// Only where a foreign key says so. A computed relationship is a function
8109/// call, and a one-to-one reached through the *child's* key is a row that may
8110/// simply not exist.
8111fn always_present(rel: &RelationshipField, table: &postrust_core::schema_cache::Table) -> bool {
8112    use postrust_core::schema_cache::{Cardinality, Relationship};
8113    let Relationship::ForeignKey { cardinality, .. } = &rel.relationship else {
8114        return false;
8115    };
8116    match cardinality {
8117        Cardinality::M2O { .. } => {}
8118        Cardinality::O2O {
8119            is_parent: false, ..
8120        } => {}
8121        _ => return false,
8122    }
8123    let columns = cardinality.columns();
8124    !columns.is_empty()
8125        && columns.iter().all(|(local, _)| {
8126            table
8127                .get_column(local)
8128                .is_some_and(|column| !column.nullable)
8129        })
8130}
8131
8132/// Whether the row at the other end of a relationship carries the key.
8133///
8134/// If it does, this row is written first and its key is pushed down; if this
8135/// row's own column carries it, the other row is written first and its key is
8136/// read back. Cardinality alone does not answer it: a one-to-one is one row in
8137/// either direction, and which side holds the foreign key is the whole
8138/// difference between the two orders.
8139fn child_holds_the_key(relationship: &postrust_core::schema_cache::Relationship) -> bool {
8140    use postrust_core::schema_cache::{Cardinality, Relationship};
8141    match relationship {
8142        Relationship::ForeignKey { cardinality, .. } => match cardinality {
8143            Cardinality::M2O { .. } => false,
8144            Cardinality::O2O { is_parent, .. } => *is_parent,
8145            _ => true,
8146        },
8147        Relationship::Computed { .. } => true,
8148    }
8149}
8150
8151/// The name of a function's session parameter, if it has one.
8152fn computed_session_argument(
8153    cache: &SchemaCache,
8154    function: &postrust_core::api_request::QualifiedIdentifier,
8155    row_argument: &str,
8156) -> Option<String> {
8157    let _ = row_argument;
8158    cache
8159        .routines
8160        .get(function)
8161        .into_iter()
8162        .flatten()
8163        .find(|routine| routine.name == function.name)?
8164        .params
8165        .iter()
8166        .find(|param| {
8167            param.name == "hasura_session" && matches!(param.param_type.as_str(), "json" | "jsonb")
8168        })
8169        .map(|param| param.name.clone())
8170}
8171
8172/// The name of the input holding a computed relationship's own arguments.
8173fn computed_args_type_name(type_name: &str, field: &str) -> String {
8174    format!("{}_{}_args", type_name, field)
8175}
8176
8177/// The argument list a computed relationship's function is called with.
8178///
8179/// `None` where the parent row is the only thing it takes -- the ordinary case,
8180/// and the positional call the REST surface writes. Anything else is written by
8181/// name: the row, the session where the function asks for one, and whatever the
8182/// client put under `args`.
8183fn computed_arguments(
8184    relationship: &RelationshipField,
8185    plan: &postrust_core::embed::EmbedPlan,
8186    parent_row: &str,
8187    given: Option<&serde_json::Value>,
8188    param_idx: &mut usize,
8189    values: &mut Vec<serde_json::Value>,
8190    schema_cache: &SchemaCache,
8191) -> Result<Option<String>, async_graphql::Error> {
8192    let (Some(function), Some(row_argument)) = (&plan.function, &plan.row_argument) else {
8193        return Ok(None);
8194    };
8195    let session = schema_cache
8196        .routines
8197        .get(function)
8198        .into_iter()
8199        .flatten()
8200        .find(|routine| routine.name == function.name)
8201        .and_then(|routine| {
8202            routine
8203                .params
8204                .iter()
8205                .find(|param| {
8206                    param.name == "hasura_session"
8207                        && matches!(param.param_type.as_str(), "json" | "jsonb")
8208                })
8209                .map(|param| param.name.clone())
8210        });
8211    if relationship.arguments.is_empty() && session.is_none() {
8212        return Ok(None);
8213    }
8214
8215    let mut passed = vec![format!(
8216        "{} => {}",
8217        postrust_sql::escape_ident(row_argument),
8218        parent_row
8219    )];
8220    if let Some(session) = session {
8221        passed.push(format!(
8222            "{} => {}",
8223            postrust_sql::escape_ident(&session),
8224            SESSION_ARGUMENT
8225        ));
8226    }
8227    for (name, pg_type, required) in &relationship.arguments {
8228        match given.and_then(|given| given.get(name)) {
8229            None if *required => {
8230                return Err(async_graphql::Error::new(format!(
8231                    "{} needs the argument \"{}\"",
8232                    relationship.name, name
8233                )))
8234            }
8235            // Left out is left out, so the function's own default applies.
8236            None => continue,
8237            Some(value) => {
8238                passed.push(format!(
8239                    "{} => ${}::{}",
8240                    postrust_sql::escape_ident(name),
8241                    param_idx,
8242                    pg_type
8243                ));
8244                *param_idx += 1;
8245                values.push(value.clone());
8246            }
8247        }
8248    }
8249    Ok(Some(passed.join(", ")))
8250}
8251
8252/// The arguments written on an embedded field, as JSON.
8253fn embed_arguments(field: async_graphql::SelectionField<'_>) -> HashMap<String, serde_json::Value> {
8254    field
8255        .arguments()
8256        .map(|args| {
8257            args.into_iter()
8258                .map(|(name, value)| {
8259                    (
8260                        name.to_string(),
8261                        plain_numbers(value.into_json().unwrap_or(serde_json::Value::Null)),
8262                    )
8263                })
8264                .collect()
8265        })
8266        .unwrap_or_default()
8267}
8268
8269/// What an embed's arguments narrow its child rows to: a `DISTINCT ON`
8270/// prefix, a predicate, an ordering, a limit and an offset, in the order the
8271/// SQL takes them.
8272type Narrowing = (String, Option<String>, Option<String>, Option<i64>, i64);
8273
8274/// What an embed's arguments narrow its child rows to.
8275///
8276/// A list and an aggregate of that same list take the same four arguments and
8277/// mean the same thing by them; only what is done with the surviving rows
8278/// differs, so the reading of the arguments is shared.
8279#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
8280fn embed_narrowing(
8281    arguments: &HashMap<String, serde_json::Value>,
8282    plan: &postrust_core::embed::EmbedPlan,
8283    schema_cache: &SchemaCache,
8284    relationships: &HashMap<String, Vec<RelationshipField>>,
8285    target_type: &str,
8286    child_alias: &str,
8287    max_rows: Option<i64>,
8288    param_idx: &mut usize,
8289    values: &mut Vec<serde_json::Value>,
8290    names: &crate::names::NameOverrides,
8291    caller: &crate::role::Caller<'_>,
8292) -> Result<Narrowing, async_graphql::Error> {
8293    // The child's own permission, which is the whole reason an embed is a
8294    // place this has to be applied: `author { articles { ... } }` reads two
8295    // tables, and the rule on the second is not the rule on the first.
8296    let permission =
8297        permission_predicate(caller, names, &plan.foreign_schema, &plan.foreign_table)?;
8298    let requested = arguments.get("where").filter(|v| !v.is_null()).cloned();
8299    let child_where = match and_predicates(requested, permission) {
8300        Some(expression) => {
8301            let child_scope = WhereScope::for_alias(
8302                &plan.foreign_schema,
8303                &plan.foreign_table,
8304                child_alias,
8305                target_type,
8306                schema_cache,
8307                relationships,
8308                names,
8309            )
8310            .for_caller(*caller);
8311            let mut nested_alias = 0usize;
8312            build_condition(
8313                &expression,
8314                &child_scope,
8315                param_idx,
8316                values,
8317                &mut nested_alias,
8318            )?
8319        }
8320        None => None,
8321    };
8322
8323    let child_order = match arguments.get("order_by") {
8324        Some(order) if !order.is_null() => order_terms(
8325            order,
8326            schema_cache,
8327            &plan.foreign_schema,
8328            &plan.foreign_table,
8329            child_alias,
8330            names,
8331        )?,
8332        _ => None,
8333    };
8334
8335    // A limit written on the embed is what the client asked for; the
8336    // configured ceiling still applies as an upper bound, the same way it
8337    // does at the top level -- and so does the child's own permission, which
8338    // bounds rows per parent here rather than per query.
8339    let ceiling = match (
8340        max_rows,
8341        crate::role::read_limit(caller, names, &plan.foreign_schema, &plan.foreign_table),
8342    ) {
8343        (Some(configured), Some(granted)) => Some(configured.min(granted)),
8344        (only, None) | (None, only) => only,
8345    };
8346    let child_limit = match arguments.get("limit").and_then(|v| v.as_i64()) {
8347        Some(requested) => match ceiling {
8348            Some(ceiling) => Some(requested.min(ceiling)),
8349            None => Some(requested),
8350        },
8351        None => ceiling,
8352    };
8353    let child_offset = arguments
8354        .get("offset")
8355        .and_then(|v| v.as_i64())
8356        .unwrap_or(0);
8357
8358    // `author { articles(distinct_on: [title], order_by: {title: asc}) }` --
8359    // one article per title, the same question the root field answers and
8360    // under the same rule about what may be ordered by.
8361    let requested = match arguments.get("distinct_on") {
8362        Some(serde_json::Value::Array(items)) => items
8363            .iter()
8364            .filter_map(|item| item.as_str())
8365            .collect::<Vec<_>>(),
8366        Some(serde_json::Value::String(one)) => vec![one.as_str()],
8367        _ => Vec::new(),
8368    };
8369    let mut distinct_on = Vec::with_capacity(requested.len());
8370    if !requested.is_empty() {
8371        let qi = postrust_core::api_request::QualifiedIdentifier::new(
8372            &plan.foreign_schema,
8373            &plan.foreign_table,
8374        );
8375        let table = schema_cache.get_table(&qi).ok_or_else(|| {
8376            async_graphql::Error::new(format!("unknown table \"{}\"", plan.foreign_table))
8377        })?;
8378        for field in requested {
8379            let column = table_column_for(names, table, field).to_string();
8380            if table.get_column(&column).is_none() {
8381                return Err(async_graphql::Error::new(format!(
8382                    "cannot take distinct on unknown column \"{}\" of \"{}\"",
8383                    field, plan.foreign_table
8384                )));
8385            }
8386            distinct_on.push(format!(
8387                "{}.{}",
8388                postrust_sql::escape_ident(child_alias),
8389                postrust_sql::escape_ident(&column)
8390            ));
8391        }
8392    }
8393    let (distinct, child_order) = distinct_on_clause(&distinct_on, child_order.as_deref())?;
8394
8395    Ok((
8396        distinct,
8397        child_where,
8398        child_order,
8399        child_limit,
8400        child_offset,
8401    ))
8402}
8403
8404#[allow(clippy::too_many_arguments)] // one parameter per SQL clause, plus the binding state
8405fn build_embed_expressions(
8406    caller: &crate::role::Caller<'_>,
8407    schema_cache: &SchemaCache,
8408    relationships: &HashMap<String, Vec<RelationshipField>>,
8409    type_name: &str,
8410    parent_alias: &str,
8411    selection: async_graphql::SelectionField<'_>,
8412    max_rows: Option<i64>,
8413    alias_counter: &mut usize,
8414    param_idx: &mut usize,
8415    values: &mut Vec<serde_json::Value>,
8416    names: &crate::names::NameOverrides,
8417) -> Result<Vec<(String, String)>, async_graphql::Error> {
8418    let Some(available) = relationships.get(type_name) else {
8419        return Ok(Vec::new());
8420    };
8421
8422    let mut expressions = Vec::new();
8423
8424    for field in selection.selection_set() {
8425        // `<relationship>_aggregate` is the same embed with an aggregate
8426        // select list, so it is resolved here rather than by a resolver of its
8427        // own -- the correlation is what makes it a per-parent answer.
8428        let aggregate_of = field
8429            .name()
8430            .strip_suffix("_aggregate")
8431            .and_then(|name| available.iter().find(|r| r.name == name && r.is_list));
8432
8433        if let Some(rel) = aggregate_of {
8434            let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
8435                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8436            *alias_counter += 1;
8437            let child_alias = format!("a{}", alias_counter);
8438
8439            // The same narrowing an embedded list takes. `where` here decides
8440            // which rows are counted, not which are returned, so it goes on the
8441            // set the aggregate reads rather than on the answer.
8442            let arguments = embed_arguments(field);
8443            let (child_distinct, child_where, child_order, child_limit, child_offset) =
8444                embed_narrowing(
8445                    &arguments,
8446                    &plan,
8447                    schema_cache,
8448                    relationships,
8449                    &rel.target_type,
8450                    &child_alias,
8451                    max_rows,
8452                    param_idx,
8453                    values,
8454                    names,
8455                    caller,
8456                )?;
8457
8458            let child_table =
8459                schema_cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
8460                    &plan.foreign_schema,
8461                    &plan.foreign_table,
8462                ));
8463            let select = nested_aggregate_select(
8464                caller,
8465                field,
8466                &child_alias,
8467                schema_cache,
8468                relationships,
8469                &rel.target_type,
8470                child_table,
8471                max_rows,
8472                alias_counter,
8473                param_idx,
8474                values,
8475                names,
8476            )?;
8477
8478            let call = computed_arguments(
8479                rel,
8480                &plan,
8481                &postrust_sql::escape_ident(parent_alias),
8482                arguments.get("args"),
8483                param_idx,
8484                values,
8485                schema_cache,
8486            )?;
8487            let expression = plan
8488                .aggregate_expression(
8489                    parent_alias,
8490                    &postrust_sql::escape_ident(parent_alias),
8491                    &child_alias,
8492                    &select,
8493                    child_limit,
8494                    child_offset,
8495                    child_where.as_deref(),
8496                    child_order.as_deref(),
8497                    call.as_deref(),
8498                    &child_distinct,
8499                )
8500                .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8501            expressions.push((field.name().to_string(), expression));
8502            continue;
8503        }
8504
8505        let Some(rel) = available.iter().find(|r| r.name == field.name()) else {
8506            continue;
8507        };
8508
8509        let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
8510            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8511
8512        *alias_counter += 1;
8513        let child_alias = format!("e{}", alias_counter);
8514
8515        let nested = build_embed_expressions(
8516            caller,
8517            schema_cache,
8518            relationships,
8519            &rel.target_type,
8520            &child_alias,
8521            field,
8522            max_rows,
8523            alias_counter,
8524            param_idx,
8525            values,
8526            names,
8527        )?;
8528
8529        // The arguments written on the embed itself.
8530        let arguments = embed_arguments(field);
8531        let (child_distinct, child_where, child_order, child_limit, child_offset) =
8532            embed_narrowing(
8533                &arguments,
8534                &plan,
8535                schema_cache,
8536                relationships,
8537                &rel.target_type,
8538                &child_alias,
8539                max_rows,
8540                param_idx,
8541                values,
8542                names,
8543                caller,
8544            )?;
8545
8546        // Leaf fields are columns; anything that resolved to a relationship is
8547        // an expression instead.
8548        let child_relationships = relationships.get(&rel.target_type);
8549        let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
8550            &plan.foreign_schema,
8551            &plan.foreign_table,
8552        );
8553        let child_table = schema_cache.get_table(&child_qi);
8554        let mut parts: Vec<String> = Vec::new();
8555        for sub in field.selection_set() {
8556            let name = sub.name();
8557            let is_relationship = child_relationships
8558                .map(|rels| {
8559                    rels.iter().any(|r| {
8560                        r.name == name || (r.is_list && format!("{}_aggregate", r.name) == name)
8561                    })
8562                })
8563                .unwrap_or(false);
8564            if is_relationship {
8565                continue;
8566            }
8567            // A computed column inside an embed is the same call, against the
8568            // child's alias rather than the table.
8569            let column = child_table
8570                .map(|t| table_column_for(names, t, name))
8571                .unwrap_or(name);
8572            let computed = child_table
8573                .filter(|t| t.get_column(column).is_none())
8574                .and_then(|t| {
8575                    let function = names
8576                        .computed_source(&t.schema, &t.name, name)
8577                        .unwrap_or(name);
8578                    t.get_computed_column(function)
8579                });
8580            match computed {
8581                Some(definition) => parts.push(format!(
8582                    "{} AS {}",
8583                    computed_call(
8584                        definition,
8585                        &format!("{}.*", postrust_sql::escape_ident(&child_alias)),
8586                        &postrust_sql::escape_ident(&child_alias),
8587                    ),
8588                    postrust_sql::escape_ident(name)
8589                )),
8590                // A column the child exposes under another name is selected as
8591                // that name, since the row it lands in is the client's.
8592                None if column != name => parts.push(format!(
8593                    "{} AS {}",
8594                    postrust_sql::escape_ident(column),
8595                    postrust_sql::escape_ident(name)
8596                )),
8597                None => parts.push(postrust_sql::escape_ident(name)),
8598            }
8599        }
8600        if parts.is_empty() && nested.is_empty() {
8601            parts.push(
8602                child_table
8603                    .and_then(|table| rename_projection(table, &child_alias, names))
8604                    .unwrap_or_else(|| format!("{}.*", postrust_sql::escape_ident(&child_alias))),
8605            );
8606        }
8607        for (field_name, expression) in nested {
8608            parts.push(format!(
8609                "{} AS {}",
8610                expression,
8611                postrust_sql::escape_ident(&field_name)
8612            ));
8613        }
8614
8615        let computed_call_arguments = computed_arguments(
8616            rel,
8617            &plan,
8618            &postrust_sql::escape_ident(parent_alias),
8619            arguments.get("args"),
8620            param_idx,
8621            values,
8622            schema_cache,
8623        )?;
8624        let expression = plan
8625            .embed_expression(
8626                parent_alias,
8627                // A computed relationship is correlated by argument rather
8628                // than by a key: the function takes the parent row, and an
8629                // alias names that row.
8630                &postrust_sql::escape_ident(parent_alias),
8631                &child_alias,
8632                &format!("{}{}", child_distinct, parts.join(", ")),
8633                child_limit,
8634                child_offset,
8635                child_where.as_deref(),
8636                child_order.as_deref(),
8637                computed_call_arguments.as_deref(),
8638            )
8639            .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8640
8641        expressions.push((rel.name.clone(), expression));
8642    }
8643
8644    Ok(expressions)
8645}
8646
8647/// What embedding a relationship needs, independent of the rows involved.
8648///
8649/// The connection is passed alongside rather than held here: embedding runs on
8650/// the request's transaction, and a shared reference to this struct could not
8651/// hand out the mutable borrow the queries need.
8652struct EmbedContext<'c> {
8653    schema_cache: &'c SchemaCache,
8654    relationships: &'c HashMap<String, Vec<RelationshipField>>,
8655    max_rows: Option<i64>,
8656}
8657
8658/// Embed the relationship fields requested on `rows`.
8659///
8660/// One query per relationship per level, not one per row: the parents' join
8661/// keys are collected and passed as a single array. Recurses so a nested
8662/// selection costs one further query per relationship at each depth.
8663fn embed_relationships<'f>(
8664    conn: &'f mut sqlx::PgConnection,
8665    ctx: &'f EmbedContext<'f>,
8666    type_name: &'f str,
8667    selection: async_graphql::SelectionField<'f>,
8668    rows: &'f mut [serde_json::Value],
8669) -> futures::future::BoxFuture<'f, Result<(), async_graphql::Error>> {
8670    Box::pin(async move {
8671        if rows.is_empty() {
8672            return Ok(());
8673        }
8674
8675        let Some(available) = ctx.relationships.get(type_name) else {
8676            return Ok(());
8677        };
8678
8679        for requested in selection.selection_set() {
8680            let Some(rel) = available.iter().find(|r| r.name == requested.name()) else {
8681                continue;
8682            };
8683
8684            let plan =
8685                postrust_core::embed::EmbedPlan::resolve(&rel.relationship, ctx.schema_cache)
8686                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8687
8688            let keys = postrust_core::embed::parent_keys(rows, &plan.local_column);
8689
8690            // Project only what the selection asked for. A GraphQL selection
8691            // names its leaf fields, so the columns are known before the query
8692            // and an unrequested column need not be read, serialised, sent and
8693            // parsed just to be dropped.
8694            //
8695            // A nested relationship joins on a column of the child row, so that
8696            // column is added even when it was not selected; it is removed again
8697            // when the response is shaped. A selection whose sub-fields cannot
8698            // all be resolved to columns falls back to every column.
8699            let mut child_columns: Vec<String> = Vec::new();
8700            let mut project_everything = false;
8701            let child_relationships = ctx.relationships.get(&rel.target_type);
8702            for field in requested.selection_set() {
8703                let name = field.name();
8704                match child_relationships.and_then(|rels| rels.iter().find(|r| r.name == name)) {
8705                    Some(nested_rel) => {
8706                        match postrust_core::embed::EmbedPlan::resolve(
8707                            &nested_rel.relationship,
8708                            ctx.schema_cache,
8709                        ) {
8710                            Ok(nested_plan) => child_columns.push(nested_plan.local_column),
8711                            Err(_) => project_everything = true,
8712                        }
8713                    }
8714                    None => child_columns.push(name.to_string()),
8715                }
8716            }
8717            if project_everything || child_columns.is_empty() {
8718                child_columns.clear();
8719            }
8720
8721            let mut grouped = if keys.is_empty() {
8722                std::collections::HashMap::new()
8723            } else {
8724                let sql = plan
8725                    .children_grouped_sql(ctx.max_rows, &child_columns)
8726                    .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8727
8728                let fetched = sqlx::query(&sql)
8729                    .bind(&keys)
8730                    .fetch_all(&mut *conn)
8731                    .await
8732                    .map_err(database_error)?;
8733
8734                // The query returns the join key and a JSON array of that key's
8735                // children, grouped by PostgreSQL rather than row by row here.
8736                use sqlx::Row;
8737                let pairs: Vec<(serde_json::Value, serde_json::Value)> = fetched
8738                    .into_iter()
8739                    .filter_map(|row| {
8740                        Some((
8741                            row.try_get::<serde_json::Value, _>(0).ok()?,
8742                            row.try_get::<serde_json::Value, _>(1).ok()?,
8743                        ))
8744                    })
8745                    .collect();
8746
8747                postrust_core::embed::group_from_aggregated(pairs)
8748            };
8749
8750            // Recurse before attaching, so nested embeds land in the values
8751            // copied onto the parents. One query serves every child row at this
8752            // level, so the rows are flattened for the call and put back
8753            // afterwards; skipped when the selection asks for nothing deeper.
8754            let has_deeper_embed = ctx
8755                .relationships
8756                .get(&rel.target_type)
8757                .map(|rels| {
8758                    requested
8759                        .selection_set()
8760                        .any(|field| rels.iter().any(|r| r.name == field.name()))
8761                })
8762                .unwrap_or(false);
8763
8764            if has_deeper_embed {
8765                let mut order: Vec<(String, usize)> = Vec::with_capacity(grouped.len());
8766                let mut flat: Vec<serde_json::Value> = Vec::new();
8767                for (key, children) in grouped.drain() {
8768                    order.push((key, children.len()));
8769                    flat.extend(children);
8770                }
8771
8772                embed_relationships(&mut *conn, ctx, &rel.target_type, requested, &mut flat)
8773                    .await?;
8774
8775                let mut rest = flat.into_iter();
8776                for (key, count) in order {
8777                    grouped.insert(key, rest.by_ref().take(count).collect());
8778                }
8779            }
8780
8781            for row in rows.iter_mut() {
8782                postrust_core::embed::attach_to_parent(row, &rel.name, &plan, &grouped);
8783            }
8784        }
8785
8786        Ok(())
8787    })
8788}
8789
8790/// Build a `where` document that addresses exactly one row by primary key.
8791///
8792/// Used by the by-PK mutations, which take the key columns as arguments instead
8793/// of a free-form `where`.
8794fn pk_where_from_args(
8795    ctx: &ResolverContext<'_>,
8796    schema_name: &str,
8797    table_name: &str,
8798    pk_columns: &[(String, String)],
8799    names: &crate::names::NameOverrides,
8800) -> Result<serde_json::Value, async_graphql::Error> {
8801    if pk_columns.is_empty() {
8802        return Err(async_graphql::Error::new(format!(
8803            "\"{}\" has no primary key, so it cannot be mutated by key",
8804            table_name
8805        )));
8806    }
8807
8808    // `update_x_by_pk` takes its key as one `pk_columns` object; the others
8809    // take the key columns as arguments of their own. Both spellings are what
8810    // a client was generated against, so both are read here.
8811    let from_object = ctx
8812        .args
8813        .try_get("pk_columns")
8814        .ok()
8815        .map(|v| accessor_to_json(&v));
8816
8817    let mut conditions = serde_json::Map::new();
8818    for (col_name, _) in pk_columns {
8819        // Under the name the key column is exposed as, on both sides: the
8820        // argument the client wrote, and the predicate handed on to the same
8821        // builder every other `where` goes through.
8822        let field = names
8823            .column(schema_name, table_name, col_name)
8824            .unwrap_or(col_name);
8825        let value = match &from_object {
8826            Some(serde_json::Value::Object(map)) => map.get(field).cloned().ok_or_else(|| {
8827                async_graphql::Error::new(format!(
8828                    "pk_columns is missing the key column \"{}\"",
8829                    field
8830                ))
8831            })?,
8832            _ => {
8833                let arg = ctx.args.try_get(field).map_err(|_| {
8834                    async_graphql::Error::new(format!(
8835                        "missing required primary key argument \"{}\"",
8836                        field
8837                    ))
8838                })?;
8839                accessor_to_json(&arg)
8840            }
8841        };
8842        conditions.insert(field.to_string(), serde_json::json!({ "_eq": value }));
8843    }
8844
8845    Ok(serde_json::Value::Object(conditions))
8846}
8847
8848/// GraphQL scalar name to use for a primary key argument of the given
8849/// PostgreSQL type.
8850///
8851/// Falls back to `String` for anything that does not map to a plain scalar --
8852/// a composite or array key cannot be expressed as a single named argument, and
8853/// the value is cast to the column's type in SQL anyway.
8854fn pk_argument_type(pg_type: &str) -> String {
8855    let rendered = crate::types::pg_type_to_graphql(pg_type).to_string();
8856    if rendered.starts_with('[') {
8857        "String".to_string()
8858    } else {
8859        rendered
8860    }
8861}
8862
8863/// Convert a GraphQL type string to a TypeRef.
8864fn graphql_type_ref(type_str: &str) -> TypeRef {
8865    // Parse type string like "[Users!]!" or "String" or "Int!"
8866    let is_list = type_str.starts_with('[');
8867    let is_nn = type_str.ends_with('!');
8868
8869    // Strip outer modifiers: first the trailing !, then the brackets
8870    let inner = if is_list {
8871        let stripped = type_str
8872            .trim_end_matches('!') // Remove outer !
8873            .trim_start_matches('[') // Remove [
8874            .trim_end_matches(']'); // Remove ]
8875        stripped
8876    } else {
8877        type_str.trim_end_matches('!')
8878    };
8879
8880    let inner_nn = inner.ends_with('!');
8881    let base_type = inner.trim_end_matches('!');
8882
8883    if is_list {
8884        if is_nn {
8885            if inner_nn {
8886                TypeRef::named_nn_list_nn(base_type)
8887            } else {
8888                TypeRef::named_list_nn(base_type)
8889            }
8890        } else if inner_nn {
8891            TypeRef::named_nn_list(base_type)
8892        } else {
8893            TypeRef::named_list(base_type)
8894        }
8895    } else if is_nn {
8896        TypeRef::named_nn(base_type)
8897    } else {
8898        TypeRef::named(base_type)
8899    }
8900}
8901
8902/// The key serde_json wraps a number in when it is keeping its digits.
8903///
8904/// This crate reads `serde_json` with `arbitrary_precision`, so a PostgreSQL
8905/// `numeric` survives a round trip through JSON with every digit it had. The
8906/// cost is on the way in: a fractional number in a *variable* is deserialized
8907/// into async-graphql's own value type as a one-key object holding the text,
8908/// because the generic path cannot know it is looking at a number. Integers
8909/// are unaffected, which is why this went unnoticed until a shape arrived by
8910/// variable and every coordinate in it read as zero.
8911const NUMBER_AS_TEXT: &str = "$serde_json::private::Number";
8912
8913/// The number an arbitrary-precision wrapper is holding, if that is what this
8914/// object is.
8915fn number_kept_as_text(
8916    map: &serde_json::Map<String, serde_json::Value>,
8917) -> Option<serde_json::Value> {
8918    if map.len() != 1 {
8919        return None;
8920    }
8921    let text = map.get(NUMBER_AS_TEXT)?.as_str()?;
8922    serde_json::from_str::<serde_json::Value>(text)
8923        .ok()
8924        .filter(|value| value.is_number())
8925}
8926
8927/// The same, applied to a whole document that came in as one.
8928fn plain_numbers(value: serde_json::Value) -> serde_json::Value {
8929    match value {
8930        serde_json::Value::Object(map) => match number_kept_as_text(&map) {
8931            Some(number) => number,
8932            None => serde_json::Value::Object(
8933                map.into_iter()
8934                    .map(|(k, v)| (k, plain_numbers(v)))
8935                    .collect(),
8936            ),
8937        },
8938        serde_json::Value::Array(items) => {
8939            serde_json::Value::Array(items.into_iter().map(plain_numbers).collect())
8940        }
8941        other => other,
8942    }
8943}
8944
8945/// Convert ValueAccessor to JSON.
8946fn accessor_to_json(accessor: &ValueAccessor<'_>) -> serde_json::Value {
8947    // Use the deserialize method if available, or convert manually
8948    if accessor.is_null() {
8949        serde_json::Value::Null
8950    } else if let Ok(b) = accessor.boolean() {
8951        serde_json::Value::Bool(b)
8952    } else if let Ok(i) = accessor.i64() {
8953        serde_json::Value::Number(i.into())
8954    } else if let Ok(f) = accessor.f64() {
8955        serde_json::Number::from_f64(f)
8956            .map(serde_json::Value::Number)
8957            .unwrap_or(serde_json::Value::Null)
8958    } else if let Ok(s) = accessor.string() {
8959        serde_json::Value::String(s.to_string())
8960    } else if let Ok(name) = accessor.enum_name() {
8961        // An enum value is not a string to `string()`, and falling through to
8962        // null made every `order_by: {name: asc}` read as an empty direction.
8963        serde_json::Value::String(name.to_string())
8964    } else if let Ok(list) = accessor.list() {
8965        serde_json::Value::Array(list.iter().map(|v| accessor_to_json(&v)).collect())
8966    } else if let Ok(obj) = accessor.object() {
8967        let map: serde_json::Map<String, serde_json::Value> = obj
8968            .iter()
8969            .map(|(k, v)| (k.to_string(), accessor_to_json(&v)))
8970            .collect();
8971        match number_kept_as_text(&map) {
8972            Some(number) => number,
8973            None => serde_json::Value::Object(map),
8974        }
8975    } else {
8976        serde_json::Value::Null
8977    }
8978}
8979
8980/// Convert async-graphql Value to JSON.
8981fn value_to_json(value: &Value) -> serde_json::Value {
8982    match value {
8983        Value::Null => serde_json::Value::Null,
8984        Value::Boolean(b) => serde_json::Value::Bool(*b),
8985        Value::Number(n) => {
8986            if let Some(i) = n.as_i64() {
8987                serde_json::Value::Number(i.into())
8988            } else if let Some(f) = n.as_f64() {
8989                serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
8990            } else {
8991                serde_json::Value::Null
8992            }
8993        }
8994        Value::String(s) => serde_json::Value::String(s.clone()),
8995        Value::List(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
8996        Value::Object(obj) => {
8997            let map: serde_json::Map<String, serde_json::Value> = obj
8998                .iter()
8999                .map(|(k, v)| (k.to_string(), value_to_json(v)))
9000                .collect();
9001            serde_json::Value::Object(map)
9002        }
9003        Value::Binary(b) => serde_json::Value::String(base64::Engine::encode(
9004            &base64::engine::general_purpose::STANDARD,
9005            b,
9006        )),
9007        Value::Enum(e) => serde_json::Value::String(e.to_string()),
9008    }
9009}
9010
9011/// Convert JSON to async-graphql Value.
9012fn json_to_value(json: serde_json::Value) -> Value {
9013    match json {
9014        serde_json::Value::Null => Value::Null,
9015        serde_json::Value::Bool(b) => Value::Boolean(b),
9016        serde_json::Value::Number(n) => {
9017            if let Some(i) = n.as_i64() {
9018                Value::Number(i.into())
9019            } else if let Some(f) = n.as_f64() {
9020                Value::Number(async_graphql::Number::from_f64(f).unwrap())
9021            } else {
9022                Value::Null
9023            }
9024        }
9025        serde_json::Value::String(s) => Value::String(s),
9026        serde_json::Value::Array(arr) => Value::List(arr.into_iter().map(json_to_value).collect()),
9027        serde_json::Value::Object(obj) => {
9028            let map: indexmap::IndexMap<async_graphql::Name, Value> = obj
9029                .into_iter()
9030                .map(|(k, v)| (async_graphql::Name::new(k), json_to_value(v)))
9031                .collect();
9032            Value::Object(map)
9033        }
9034    }
9035}
9036
9037/// Register filter input types.
9038///
9039/// These are currently unreachable: the `filter` and `where` arguments are
9040/// declared as the `JSON` scalar, so no field references these input objects
9041/// and async-graphql prunes them from the published schema (introspecting
9042/// `IntFilterInput` returns null). They are kept as the shape to move to if
9043/// filters become typed per column; until then the operators a filter actually
9044/// supports are the ones `build_where_clause` implements, and it rejects
9045/// anything else rather than ignoring it.
9046#[cfg(test)]
9047mod tests {
9048    use super::*;
9049    use indexmap::IndexMap;
9050    use postrust_core::schema_cache::{Column, Table};
9051    use std::collections::{HashMap, HashSet};
9052
9053    fn create_test_table(name: &str) -> Table {
9054        let mut columns = IndexMap::new();
9055        columns.insert(
9056            "id".into(),
9057            Column {
9058                name: "id".into(),
9059                description: None,
9060                nullable: false,
9061                data_type: "integer".into(),
9062                nominal_type: "int4".into(),
9063                max_len: None,
9064                default: Some("nextval('id_seq')".into()),
9065                enum_values: vec![],
9066                is_pk: true,
9067                position: 1,
9068                always_generated: false,
9069                domain_type: None,
9070            },
9071        );
9072        columns.insert(
9073            "name".into(),
9074            Column {
9075                name: "name".into(),
9076                description: None,
9077                nullable: false,
9078                data_type: "text".into(),
9079                nominal_type: "text".into(),
9080                max_len: None,
9081                default: None,
9082                enum_values: vec![],
9083                is_pk: false,
9084                position: 2,
9085                always_generated: false,
9086                domain_type: None,
9087            },
9088        );
9089
9090        Table {
9091            schema: "public".into(),
9092            name: name.into(),
9093            description: None,
9094            is_view: false,
9095            insertable: true,
9096            updatable: true,
9097            deletable: true,
9098            pk_cols: vec!["id".into()],
9099            unique_constraints: Vec::new(),
9100            columns,
9101            computed_columns: Default::default(),
9102            is_partitioned: false,
9103        }
9104    }
9105
9106    fn create_test_schema_cache() -> SchemaCache {
9107        let mut tables = HashMap::new();
9108        let users = create_test_table("users");
9109        tables.insert(users.qualified_identifier(), users);
9110
9111        SchemaCache {
9112            tables,
9113            relationships: HashMap::new(),
9114            routines: HashMap::new(),
9115            timezones: HashSet::new(),
9116            media_handlers: HashMap::new(),
9117            pg_version: 150000,
9118            representations: Default::default(),
9119        }
9120    }
9121
9122    // ============================================================================
9123    // Type Reference Tests
9124    // ============================================================================
9125
9126    #[test]
9127    fn test_graphql_type_ref_simple() {
9128        let _type_ref = graphql_type_ref("String");
9129        // TypeRef doesn't implement PartialEq, so we just test it doesn't panic
9130    }
9131
9132    #[test]
9133    fn test_graphql_type_ref_non_null() {
9134        let _type_ref = graphql_type_ref("String!");
9135    }
9136
9137    #[test]
9138    fn test_graphql_type_ref_list() {
9139        let _type_ref = graphql_type_ref("[String]");
9140    }
9141
9142    #[test]
9143    fn test_graphql_type_ref_list_non_null() {
9144        let _type_ref = graphql_type_ref("[String!]!");
9145    }
9146
9147    // ============================================================================
9148    // Value Conversion Tests
9149    // ============================================================================
9150
9151    #[test]
9152    fn test_value_to_json_null() {
9153        let value = Value::Null;
9154        let json = value_to_json(&value);
9155        assert_eq!(json, serde_json::Value::Null);
9156    }
9157
9158    #[test]
9159    fn test_value_to_json_boolean() {
9160        let value = Value::Boolean(true);
9161        let json = value_to_json(&value);
9162        assert_eq!(json, serde_json::Value::Bool(true));
9163    }
9164
9165    #[test]
9166    fn test_value_to_json_number() {
9167        let value = Value::Number(42.into());
9168        let json = value_to_json(&value);
9169        assert_eq!(json, serde_json::json!(42));
9170    }
9171
9172    #[test]
9173    fn test_value_to_json_string() {
9174        let value = Value::String("hello".to_string());
9175        let json = value_to_json(&value);
9176        assert_eq!(json, serde_json::Value::String("hello".to_string()));
9177    }
9178
9179    #[test]
9180    fn test_value_to_json_list() {
9181        let value = Value::List(vec![Value::Number(1.into()), Value::Number(2.into())]);
9182        let json = value_to_json(&value);
9183        assert_eq!(json, serde_json::json!([1, 2]));
9184    }
9185
9186    #[test]
9187    fn test_json_to_value_null() {
9188        let json = serde_json::Value::Null;
9189        let value = json_to_value(json);
9190        assert!(matches!(value, Value::Null));
9191    }
9192
9193    #[test]
9194    fn test_json_to_value_boolean() {
9195        let json = serde_json::Value::Bool(false);
9196        let value = json_to_value(json);
9197        assert!(matches!(value, Value::Boolean(false)));
9198    }
9199
9200    #[test]
9201    fn test_json_to_value_number() {
9202        let json = serde_json::json!(123);
9203        let value = json_to_value(json);
9204        assert!(matches!(value, Value::Number(_)));
9205    }
9206
9207    #[test]
9208    fn test_json_to_value_string() {
9209        let json = serde_json::Value::String("test".to_string());
9210        let value = json_to_value(json);
9211        assert!(matches!(value, Value::String(_)));
9212    }
9213
9214    #[test]
9215    fn test_json_to_value_array() {
9216        let json = serde_json::json!([1, 2, 3]);
9217        let value = json_to_value(json);
9218        assert!(matches!(value, Value::List(_)));
9219    }
9220
9221    #[test]
9222    fn test_json_to_value_object() {
9223        let json = serde_json::json!({"key": "value"});
9224        let value = json_to_value(json);
9225        assert!(matches!(value, Value::Object(_)));
9226    }
9227
9228    // ============================================================================
9229    // Schema Building Tests
9230    // ============================================================================
9231
9232    #[test]
9233    fn test_build_dynamic_schema() {
9234        let cache = create_test_schema_cache();
9235        let config = SchemaConfig::default();
9236        let generated = build_schema(&cache, &config);
9237
9238        let result = build_dynamic_schema(
9239            &generated,
9240            &cache,
9241            None,
9242            None,
9243            Arc::new(Default::default()),
9244            std::time::Duration::from_secs(30),
9245            None,
9246        );
9247        if let Err(ref e) = result {
9248            eprintln!("Schema build error: {:?}", e);
9249        }
9250        assert!(result.is_ok(), "Schema build failed: {:?}", result.err());
9251    }
9252
9253    /// `_exists` becomes a subselect over the table it names, correlated with
9254    /// nothing -- which is the difference between it and a relationship.
9255    #[test]
9256    fn exists_is_a_subselect_over_another_table() {
9257        let cache = create_test_schema_cache();
9258        let names = crate::names::NameOverrides::default();
9259        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
9260        let scope = WhereScope::table("public", "users", "users", &names)
9261            .with_resolution(&cache, &relationships);
9262
9263        let mut param_idx = 1usize;
9264        let mut values: Vec<serde_json::Value> = Vec::new();
9265        let mut aliases = 0usize;
9266        let sql = build_condition(
9267            &serde_json::json!({
9268                "_exists": {"_table": "users", "_where": {"name": {"_eq": "Ann"}}}
9269            }),
9270            &scope,
9271            &mut param_idx,
9272            &mut values,
9273            &mut aliases,
9274        )
9275        .expect("compiles")
9276        .expect("constrains something");
9277
9278        assert_eq!(
9279            sql,
9280            "EXISTS (SELECT 1 FROM \"public\".\"users\" AS \"pgrst_exists_1\" \
9281             WHERE \"pgrst_exists_1\".\"name\" = $1::text)"
9282        );
9283        assert_eq!(values, vec![serde_json::json!("Ann")]);
9284    }
9285
9286    /// A `_table` nobody has is refused rather than written into the query.
9287    #[test]
9288    fn exists_names_a_table_the_server_has() {
9289        let cache = create_test_schema_cache();
9290        let names = crate::names::NameOverrides::default();
9291        let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
9292        let scope = WhereScope::table("public", "users", "users", &names)
9293            .with_resolution(&cache, &relationships);
9294        let error = build_condition(
9295            &serde_json::json!({"_exists": {"_table": "nowhere", "_where": {}}}),
9296            &scope,
9297            &mut 1,
9298            &mut Vec::new(),
9299            &mut 0,
9300        )
9301        .expect_err("a table that is not there is an error");
9302        assert!(
9303            error.message.contains("public.nowhere"),
9304            "{}",
9305            error.message
9306        );
9307    }
9308
9309    /// A role granted "how many" and not "which" gets the count and no rows.
9310    ///
9311    /// A select permission naming no columns, with `allow_aggregations`, is
9312    /// Hasura's way of writing that. The table has an aggregate root and no
9313    /// row type, so `nodes` is not there to be a list of one -- and `count`
9314    /// takes no `columns`, because there are none to name.
9315    #[test]
9316    fn a_table_a_role_may_count_and_not_read_has_a_count_and_no_rows() {
9317        let cache = create_test_schema_cache();
9318        let names = crate::names::NameOverrides::parse(
9319            r#"{"tables": {"public.users": {"permissions":
9320                 {"counter": {"select": {"columns": [], "filter": {},
9321                                         "allow_aggregations": true}}}}}}"#,
9322        )
9323        .unwrap();
9324        let view = crate::role::cache_for_role(&cache, &names, "counter", false);
9325        assert!(!view.tables.is_empty(), "the table is there to be counted");
9326
9327        let config = SchemaConfig {
9328            names: names.clone(),
9329            role: Some("counter".to_string()),
9330            ..SchemaConfig::default()
9331        };
9332        let generated = build_schema(&view, &config);
9333        let schema = build_dynamic_schema(
9334            &generated,
9335            &view,
9336            None,
9337            None,
9338            Arc::new(names),
9339            std::time::Duration::from_secs(30),
9340            Some("counter"),
9341        )
9342        .expect("a role that may only count still has a buildable schema");
9343        let sdl = schema.sdl();
9344
9345        assert!(
9346            sdl.contains("users_aggregate"),
9347            "the count is there:\n{}",
9348            sdl
9349        );
9350        assert!(sdl.contains("count: Int!"), "and it is a count:\n{}", sdl);
9351        // No row type, so nothing that would answer with one.
9352        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
9353        assert!(
9354            !sdl.contains("nodes"),
9355            "and no rows beside the numbers:\n{}",
9356            sdl
9357        );
9358        assert!(
9359            !sdl.contains("users_select_column"),
9360            "no column enum to order by or count over:\n{}",
9361            sdl
9362        );
9363        // The functions that read column data are absent with the columns.
9364        assert!(!sdl.contains("users_max_fields"), "no max:\n{}", sdl);
9365    }
9366
9367    /// A role granted `insert` and no `select` gets the write and no type.
9368    ///
9369    /// Hasura's own corpus does this eight times over. What has to hold is
9370    /// that the schema still builds: `insert_users` is there, `users` is not,
9371    /// and the response the insert answers with has no `returning` to name a
9372    /// type that does not exist.
9373    #[test]
9374    fn a_table_a_role_may_only_write_gets_the_write_and_no_type() {
9375        let cache = create_test_schema_cache();
9376        let names = crate::names::NameOverrides::parse(
9377            r#"{"tables": {"public.users": {"permissions":
9378                 {"writer": {"insert": {"columns": "*", "check": {}}}}}}}"#,
9379        )
9380        .unwrap();
9381        let view = crate::role::cache_for_role(&cache, &names, "writer", false);
9382        let config = SchemaConfig {
9383            names: names.clone(),
9384            role: Some("writer".to_string()),
9385            ..SchemaConfig::default()
9386        };
9387        let generated = build_schema(&view, &config);
9388
9389        let schema = build_dynamic_schema(
9390            &generated,
9391            &view,
9392            None,
9393            None,
9394            Arc::new(names),
9395            std::time::Duration::from_secs(30),
9396            Some("writer"),
9397        )
9398        .expect("a role that may only write still has a buildable schema");
9399        let sdl = schema.sdl();
9400
9401        assert!(
9402            sdl.contains("insert_users("),
9403            "the write is there:\n{}",
9404            sdl
9405        );
9406        assert!(
9407            sdl.contains("users_insert_input"),
9408            "and its input:\n{}",
9409            sdl
9410        );
9411        // No row type, and so none of the three fields that answer with one.
9412        assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
9413        assert!(!sdl.contains("insert_users_one"), "no insert_one:\n{}", sdl);
9414        assert!(
9415            !sdl.contains("returning"),
9416            "and nothing to return:\n{}",
9417            sdl
9418        );
9419        assert!(
9420            sdl.contains("users_mutation_response"),
9421            "the count is still answered:\n{}",
9422            sdl
9423        );
9424        assert!(
9425            !sdl.contains("_mutation_response_mutation_response"),
9426            "and no type is named twice over:\n{}",
9427            sdl
9428        );
9429    }
9430
9431    /// A cache with one VOLATILE function returning rows of `users`, which is
9432    /// the shape `add_to_score` has in Hasura's corpus.
9433    fn cache_with_a_mutation_function() -> SchemaCache {
9434        use postrust_core::schema_cache::{FuncVolatility, RetType, Routine};
9435
9436        let mut cache = create_test_schema_cache();
9437        let routine = Routine {
9438            schema: "public".into(),
9439            name: "add_to_score".into(),
9440            description: None,
9441            params: Vec::new(),
9442            return_type: RetType::SetOf("users".into()),
9443            returns_composite: true,
9444            volatility: FuncVolatility::Volatile,
9445            has_variadic: false,
9446            isolation_level: None,
9447            settings: Vec::new(),
9448            is_procedure: false,
9449            media_type: None,
9450            media_base_type: None,
9451            output_columns: Vec::new(),
9452        };
9453        cache
9454            .routines
9455            .insert(routine.qualified_identifier(), vec![routine]);
9456        cache
9457    }
9458
9459    /// A function with side effects is not granted by permission to read the
9460    /// table it returns. Hasura infers a query function's permission from
9461    /// that select and infers nothing for a mutation, so a role never named
9462    /// by a function permission has no mutation root at all.
9463    #[test]
9464    fn a_mutation_function_needs_the_role_to_be_named() {
9465        let cache = cache_with_a_mutation_function();
9466        let withheld = crate::names::NameOverrides::parse(
9467            r#"{"tables": {"public.users": {"permissions":
9468                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
9469                "functions": {"public.add_to_score": {"exposed_as": "mutation"}}}"#,
9470        )
9471        .unwrap();
9472        let granted = crate::names::NameOverrides::parse(
9473            r#"{"tables": {"public.users": {"permissions":
9474                 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
9475                "functions": {"public.add_to_score":
9476                  {"exposed_as": "mutation", "roles": ["reader"]}}}"#,
9477        )
9478        .unwrap();
9479
9480        let sdl_for = |names: &crate::names::NameOverrides| {
9481            let view = crate::role::cache_for_role(&cache, names, "reader", false);
9482            let config = SchemaConfig {
9483                names: names.clone(),
9484                role: Some("reader".to_string()),
9485                ..SchemaConfig::default()
9486            };
9487            let generated = build_schema(&view, &config);
9488            build_dynamic_schema(
9489                &generated,
9490                &view,
9491                None,
9492                None,
9493                Arc::new(names.clone()),
9494                std::time::Duration::from_secs(30),
9495                Some("reader"),
9496            )
9497            .expect("schema builds")
9498            .sdl()
9499        };
9500
9501        let without = sdl_for(&withheld);
9502        assert!(
9503            !without.contains("add_to_score"),
9504            "not granted, so not there:\n{}",
9505            without
9506        );
9507        // And with no mutation of any kind left, there is no mutation root --
9508        // which is what `no mutations exist` is answered from.
9509        assert!(
9510            !without.contains("mutation: mutation_root"),
9511            "and no mutation root at all:\n{}",
9512            without
9513        );
9514
9515        let with = sdl_for(&granted);
9516        assert!(
9517            with.contains("add_to_score"),
9518            "named by a function permission, so it is:\n{}",
9519            with
9520        );
9521    }
9522
9523    /// An unrecognised role is a role that was granted nothing, not an error.
9524    #[test]
9525    fn a_role_the_document_never_names_is_granted_nothing() {
9526        let cache = Arc::new(create_test_schema_cache());
9527        let config = SchemaConfig {
9528            names: crate::names::NameOverrides::parse(
9529                r#"{"tables": {"public.users": {"permissions":
9530                     {"reader": {"select": {"columns": "*", "filter": {}}}}}}}"#,
9531            )
9532            .unwrap(),
9533            ..SchemaConfig::default()
9534        };
9535        let empty = build_ungranted_schema(&cache, &config).expect("it builds");
9536        let sdl = empty.sdl();
9537        assert!(
9538            sdl.contains("no_queries_available"),
9539            "nothing to read:\n{}",
9540            sdl
9541        );
9542        assert!(!sdl.contains("type users "), "and no table:\n{}", sdl);
9543        // A document with no permissions at all has no unrecognised roles.
9544        assert!(build_ungranted_schema(&cache, &SchemaConfig::default()).is_none());
9545    }
9546
9547    /// A column the role may write and not read is in the input and not in the
9548    /// type. The two column sets are the point: one schema cache, two answers.
9549    #[test]
9550    fn a_column_a_role_may_set_without_seeing_is_in_the_input_alone() {
9551        let cache = create_test_schema_cache();
9552        let names = crate::names::NameOverrides::parse(
9553            r#"{"tables": {"public.users": {"permissions":
9554                 {"user": {"select": {"columns": ["id"], "filter": {}},
9555                           "insert": {"columns": "*", "check": {}}}}}}}"#,
9556        )
9557        .unwrap();
9558        let view = crate::role::cache_for_role(&cache, &names, "user", false);
9559        let config = SchemaConfig {
9560            names: names.clone(),
9561            role: Some("user".to_string()),
9562            ..SchemaConfig::default()
9563        };
9564        let generated = build_schema(&view, &config);
9565        let object = &generated.object_types["users"];
9566        assert_eq!(
9567            object
9568                .fields
9569                .iter()
9570                .map(|f| f.name.as_str())
9571                .collect::<Vec<_>>(),
9572            vec!["id"],
9573            "the type shows what may be read"
9574        );
9575        assert!(
9576            object.writable_fields.iter().any(|f| f.name == "name"),
9577            "and the input keeps what may be written"
9578        );
9579
9580        let schema = build_dynamic_schema(
9581            &generated,
9582            &view,
9583            None,
9584            None,
9585            Arc::new(names),
9586            std::time::Duration::from_secs(30),
9587            Some("user"),
9588        )
9589        .expect("schema builds");
9590        let sdl = schema.sdl();
9591        let input = sdl
9592            .split("input users_insert_input")
9593            .nth(1)
9594            .and_then(|rest| rest.split('}').next())
9595            .expect("the insert input is registered");
9596        assert!(input.contains("name:"), "settable without being readable");
9597    }
9598
9599    /// A column PostgreSQL generates always is in the row type and in no
9600    /// write input. It is not a permission -- no role may name it -- so the
9601    /// refusal belongs to the schema rather than to the database.
9602    #[test]
9603    fn a_generated_column_is_readable_and_not_writable() {
9604        let mut cache = create_test_schema_cache();
9605        cache
9606            .tables
9607            .get_mut(&postrust_core::api_request::QualifiedIdentifier::new(
9608                "public", "users",
9609            ))
9610            .expect("the fixture has users")
9611            .columns
9612            .get_mut("id")
9613            .expect("with an id")
9614            .always_generated = true;
9615
9616        let names = crate::names::NameOverrides::default();
9617        let config = SchemaConfig {
9618            names: names.clone(),
9619            ..SchemaConfig::default()
9620        };
9621        let generated = build_schema(&cache, &config);
9622        let schema = build_dynamic_schema(
9623            &generated,
9624            &cache,
9625            None,
9626            None,
9627            Arc::new(names),
9628            std::time::Duration::from_secs(30),
9629            None,
9630        )
9631        .expect("schema builds");
9632        let sdl = schema.sdl();
9633
9634        let block = |header: &str| {
9635            sdl.split(header)
9636                .nth(1)
9637                .and_then(|rest| rest.split('}').next())
9638                .unwrap_or_else(|| panic!("{} is registered:\n{}", header, sdl))
9639                .to_string()
9640        };
9641        assert!(
9642            block("type users ").contains("id:"),
9643            "still readable:\n{}",
9644            sdl
9645        );
9646        for header in ["input users_insert_input", "input users_set_input"] {
9647            assert!(
9648                !block(header).contains("id:"),
9649                "{} may not name it:\n{}",
9650                header,
9651                block(header)
9652            );
9653        }
9654        // The table's only number was the generated column, so there is
9655        // nothing left to add to and no type for adding to it.
9656        assert!(
9657            !sdl.contains("users_inc_input"),
9658            "and nothing is left to increment:\n{}",
9659            sdl
9660        );
9661        assert!(
9662            block("input users_insert_input").contains("name:"),
9663            "and the rest of the table is untouched",
9664        );
9665    }
9666
9667    #[test]
9668    fn test_create_object_type() {
9669        let table = create_test_table("users");
9670        let obj = TableObjectType::from_table(&table);
9671        let _gql_obj = create_object_type(&obj, &[]);
9672    }
9673
9674    #[test]
9675    fn test_create_query_type() {
9676        let cache = create_test_schema_cache();
9677        let config = SchemaConfig::default();
9678        let generated = build_schema(&cache, &config);
9679
9680        let _query = create_query_type(
9681            &generated,
9682            None,
9683            Arc::new(HashMap::new()),
9684            Arc::new(Default::default()),
9685        );
9686    }
9687
9688    #[test]
9689    fn test_create_mutation_type() {
9690        let cache = create_test_schema_cache();
9691        let config = SchemaConfig::default();
9692        let generated = build_schema(&cache, &config);
9693
9694        let _mutation = create_mutation_type(
9695            &generated,
9696            Arc::new(HashMap::new()),
9697            Arc::new(HashMap::new()),
9698            Arc::new(Default::default()),
9699            None,
9700            None,
9701        );
9702    }
9703
9704    // ============================================================================
9705    // Scalar Tests
9706    // ============================================================================
9707
9708    #[test]
9709    fn a_scalar_is_named_the_way_a_client_declares_it() {
9710        use crate::types::{pg_type_to_graphql, GraphQLType};
9711
9712        // These names appear in the client's own queries -- `query ($x:
9713        // jsonb!)` names a type that has to exist under exactly that
9714        // spelling -- so they are asserted rather than left to the Display
9715        // impl.
9716        assert_eq!(pg_type_to_graphql("jsonb").to_string(), "jsonb");
9717        assert_eq!(pg_type_to_graphql("json").to_string(), "json");
9718        assert_eq!(pg_type_to_graphql("int8").to_string(), "bigint");
9719        assert_eq!(pg_type_to_graphql("numeric").to_string(), "numeric");
9720        assert_eq!(pg_type_to_graphql("timestamptz").to_string(), "timestamptz");
9721        assert_eq!(pg_type_to_graphql("timestamp").to_string(), "timestamp");
9722        assert_eq!(pg_type_to_graphql("uuid").to_string(), "uuid");
9723
9724        // A type this server knows nothing about keeps its own name, which is
9725        // how a PostGIS column and a database enum both become usable.
9726        assert_eq!(pg_type_to_graphql("geometry").to_string(), "geometry");
9727        assert_eq!(
9728            pg_type_to_graphql("colors_enum"),
9729            GraphQLType::Custom("colors_enum".to_string())
9730        );
9731
9732        // And the leaf of an array is what gets registered, not the array.
9733        assert_eq!(
9734            leaf_scalar_name(&pg_type_to_graphql("_text")),
9735            "String".to_string()
9736        );
9737    }
9738
9739    // ============================================================================
9740    // Filter Input Type Tests
9741    // ============================================================================
9742
9743    #[test]
9744    fn every_table_gets_a_boolean_expression() {
9745        let cache = create_test_schema_cache();
9746        let config = SchemaConfig::default();
9747        let generated = build_schema(&cache, &config);
9748
9749        let (inputs, _) = crate::input::bool_exp::build_inputs(
9750            &generated.object_types,
9751            &generated.relationship_fields,
9752        );
9753        let names: HashSet<String> = inputs.iter().map(|i| i.type_name().to_string()).collect();
9754
9755        for table in generated.object_types.keys() {
9756            let expected = format!("{}_bool_exp", table);
9757            assert!(
9758                names.contains(&expected),
9759                "no {} among {:?}",
9760                expected,
9761                names
9762            );
9763        }
9764        // The scalars the fixture's columns use.
9765        assert!(names.contains("String_comparison_exp"));
9766        assert!(names.contains("Int_comparison_exp"));
9767    }
9768
9769    #[test]
9770    fn a_schema_carrying_the_boolean_expressions_still_builds() {
9771        let cache = create_test_schema_cache();
9772        let config = SchemaConfig::default();
9773        let generated = build_schema(&cache, &config);
9774
9775        let schema = build_dynamic_schema(
9776            &generated,
9777            &cache,
9778            None,
9779            None,
9780            Arc::new(Default::default()),
9781            std::time::Duration::from_secs(30),
9782            None,
9783        );
9784        assert!(schema.is_ok(), "{:?}", schema.err());
9785
9786        let sdl = schema.unwrap().sdl();
9787        assert!(
9788            sdl.contains("_bool_exp"),
9789            "no boolean expressions in:\n{}",
9790            sdl
9791        );
9792        assert!(sdl.contains("_and"), "no _and in the generated expressions");
9793    }
9794
9795    // ============================================================================
9796    // Subscription Tests
9797    // ============================================================================
9798
9799    #[test]
9800    fn test_build_schema_with_subscriptions() {
9801        let cache = create_test_schema_cache();
9802        let config = SchemaConfig {
9803            enable_subscriptions: true,
9804            ..SchemaConfig::default()
9805        };
9806        let generated = build_schema(&cache, &config);
9807
9808        // Generate subscription fields
9809        let sub_fields = generate_subscription_fields(&cache, &generated);
9810        assert!(!sub_fields.is_empty(), "Should have subscription fields");
9811
9812        // Build schema with subscriptions
9813        let result = build_dynamic_schema(
9814            &generated,
9815            &cache,
9816            Some(&sub_fields),
9817            None,
9818            Arc::new(Default::default()),
9819            std::time::Duration::from_secs(30),
9820            None,
9821        );
9822        assert!(result.is_ok(), "Schema with subscriptions should build");
9823    }
9824
9825    #[test]
9826    fn test_subscription_field_generation() {
9827        let cache = create_test_schema_cache();
9828        let config = SchemaConfig::default();
9829        let generated = build_schema(&cache, &config);
9830
9831        let fields = generate_subscription_fields(&cache, &generated);
9832
9833        // Should have one subscription field for the users table
9834        assert_eq!(fields.len(), 1);
9835        assert_eq!(fields[0].name, "users");
9836        assert_eq!(fields[0].table_name, "users");
9837        assert_eq!(fields[0].channel_name(), "postrust_public_users");
9838    }
9839
9840    #[test]
9841    fn test_create_subscription_type() {
9842        let cache = create_test_schema_cache();
9843        let config = SchemaConfig::default();
9844        let generated = build_schema(&cache, &config);
9845        // A live query per queryable root: the subscription root mirrors the
9846        // query root, which is the whole contract.
9847        let _subscription = create_subscription_type(
9848            &generated,
9849            None,
9850            Arc::new(Default::default()),
9851            Arc::new(Default::default()),
9852            std::time::Duration::from_secs(30),
9853        );
9854    }
9855}
9856
9857/// One step of a JSON path: a key of an object or an index of an array.
9858#[derive(Debug, PartialEq, Eq)]
9859enum PathStep {
9860    Key(String),
9861    Index(usize),
9862}
9863
9864/// Read one part of a document, by the path spelling Hasura accepts.
9865///
9866/// `column(path: "objs[0]['你好']")` -- an optional leading `$`, then keys
9867/// written bare or after a dot, and indices or quoted keys written in
9868/// brackets. A step that names something the document does not have is null,
9869/// which is what `#>` answers to the same path.
9870fn walk_json_path(value: &Value, path: &str) -> Result<Value, async_graphql::Error> {
9871    let mut at = value;
9872    for step in parse_json_path(path)? {
9873        let next = match (&step, at) {
9874            (PathStep::Key(key), Value::Object(map)) => map.get(key.as_str()),
9875            (PathStep::Index(index), Value::List(items)) => items.get(*index),
9876            // An index of an object or a key of an array reads nothing, and so
9877            // does anything asked of a scalar.
9878            _ => None,
9879        };
9880        match next {
9881            Some(next) => at = next,
9882            None => return Ok(Value::Null),
9883        }
9884    }
9885    Ok(at.clone())
9886}
9887
9888/// A path whose bracket never closes: written wrong rather than written oddly.
9889fn unclosed_bracket(path: &str) -> async_graphql::Error {
9890    let message = format!("\"{}\" is not a JSON path: a bracket is never closed", path);
9891    validation_error(&message)
9892}
9893
9894/// Parse the path spelling Hasura accepts into its steps.
9895///
9896/// Deliberately tolerant about separators, because the corpus writes the same
9897/// path four ways: `objs[0]["x"]`, `objs[0].["x"]`, `objs.[0].["x"]` and
9898/// `.objs[0]['x']` all name one thing. What is not tolerated is a bracket that
9899/// never closes, which is a path the client got wrong rather than a spelling.
9900fn parse_json_path(path: &str) -> Result<Vec<PathStep>, async_graphql::Error> {
9901    let mut steps = Vec::new();
9902    let mut chars = path.chars().peekable();
9903    // `$` is the document itself, and naming it is optional.
9904    if chars.peek() == Some(&'$') {
9905        chars.next();
9906    }
9907    while let Some(&c) = chars.peek() {
9908        match c {
9909            // A separator carries no step of its own.
9910            '.' => {
9911                chars.next();
9912            }
9913            '[' => {
9914                chars.next();
9915                let quote = match chars.peek() {
9916                    Some(&q @ ('\'' | '"')) => {
9917                        chars.next();
9918                        Some(q)
9919                    }
9920                    _ => None,
9921                };
9922                let mut inner = String::new();
9923                loop {
9924                    let Some(c) = chars.next() else {
9925                        return Err(unclosed_bracket(path));
9926                    };
9927                    match (quote, c) {
9928                        (Some(quote), c) if c == quote => break,
9929                        (None, ']') => break,
9930                        _ => inner.push(c),
9931                    }
9932                }
9933                // A quoted key's closing bracket, which the quote ended before.
9934                if quote.is_some() {
9935                    match chars.next() {
9936                        Some(']') => {}
9937                        _ => return Err(unclosed_bracket(path)),
9938                    }
9939                }
9940                // An unquoted number is an index; anything else is a key,
9941                // since an object may be keyed by a word as well as a digit.
9942                match (quote, inner.parse::<usize>()) {
9943                    (None, Ok(index)) => steps.push(PathStep::Index(index)),
9944                    _ => steps.push(PathStep::Key(inner)),
9945                }
9946            }
9947            _ => {
9948                let mut key = String::new();
9949                while let Some(&c) = chars.peek() {
9950                    if matches!(c, '.' | '[') {
9951                        break;
9952                    }
9953                    key.push(c);
9954                    chars.next();
9955                }
9956                steps.push(PathStep::Key(key));
9957            }
9958        }
9959    }
9960    Ok(steps)
9961}
9962
9963#[cfg(test)]
9964mod json_path_tests {
9965    use super::*;
9966
9967    fn steps(path: &str) -> Vec<PathStep> {
9968        parse_json_path(path).expect("the path parses")
9969    }
9970
9971    fn key(name: &str) -> PathStep {
9972        PathStep::Key(name.to_string())
9973    }
9974
9975    #[test]
9976    fn the_document_itself_has_no_steps() {
9977        assert_eq!(steps("$"), vec![]);
9978        assert_eq!(steps(""), vec![]);
9979    }
9980
9981    #[test]
9982    fn a_key_is_written_bare_or_after_a_dot() {
9983        assert_eq!(steps("a"), vec![key("a")]);
9984        assert_eq!(steps(".obj.c1"), vec![key("obj"), key("c1")]);
9985        assert_eq!(steps("._underscore"), vec![key("_underscore")]);
9986    }
9987
9988    #[test]
9989    fn a_bracket_holds_an_index_or_a_quoted_key() {
9990        assert_eq!(steps("arr[0]"), vec![key("arr"), PathStep::Index(0)]);
9991        assert_eq!(steps("['!@#$%^']"), vec![key("!@#$%^")]);
9992        assert_eq!(
9993            steps("translations['hello world!']"),
9994            vec![key("translations"), key("hello world!")]
9995        );
9996    }
9997
9998    /// The corpus writes one path four ways, and Hasura reads them all alike.
9999    #[test]
10000    fn a_dot_before_a_bracket_changes_nothing() {
10001        let expected = vec![key("objs"), PathStep::Index(0), key("\u{4f60}\u{597d}")];
10002        for spelling in [
10003            "objs[0]['\u{4f60}\u{597d}']",
10004            "objs[0][\"\u{4f60}\u{597d}\"]",
10005            "objs[0].[\"\u{4f60}\u{597d}\"]",
10006            "objs.[0].[\"\u{4f60}\u{597d}\"]",
10007        ] {
10008            assert_eq!(steps(spelling), expected, "{}", spelling);
10009        }
10010    }
10011
10012    #[test]
10013    fn a_bracket_that_never_closes_is_refused() {
10014        assert!(parse_json_path("arr[0").is_err());
10015        assert!(parse_json_path("['key'").is_err());
10016    }
10017
10018    #[test]
10019    fn a_step_the_document_does_not_have_reads_null() {
10020        let document = Value::from_json(serde_json::json!({
10021            "obj": {"c1": "c2"},
10022            "arr": [1, 2, 3],
10023        }))
10024        .expect("a document");
10025        let at = |path| walk_json_path(&document, path).expect("the path parses");
10026        assert_eq!(at(".obj.c1"), Value::String("c2".to_string()));
10027        assert_eq!(at("arr[1]"), Value::Number(2.into()));
10028        assert_eq!(at("arr[9]"), Value::Null);
10029        assert_eq!(at(".obj.nothing"), Value::Null);
10030        // An index of an object, and a key of an array: neither reads
10031        // anything, which is what `#>` answers.
10032        assert_eq!(at("obj[0]"), Value::Null);
10033        assert_eq!(at("arr.first"), Value::Null);
10034    }
10035}