1use crate::context::GraphQLContext;
7use crate::error::GraphQLError;
8use crate::schema::object::TableObjectType;
9use crate::schema::relationship::RelationshipField;
10use crate::schema::{build_schema, FederationEntity, GeneratedSchema, MutationType, SchemaConfig};
11use crate::subscription::{
12 generate_subscription_fields, NotifyBroker, SubscriptionField as SubField,
13};
14use async_graphql::dynamic::*;
15use async_graphql::extensions::{
16 Extension, ExtensionContext, ExtensionFactory, NextResolve, ResolveInfo,
17};
18use async_graphql::parser::types::{FragmentDefinition, Selection, SelectionSet, TypeCondition};
19use async_graphql::{Name, Positioned, SelectionField, ServerResult, Value};
20use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
21use axum::extract::State;
22use axum::response::IntoResponse;
23use postrust_core::schema_cache::SchemaCache;
24use sqlx::PgPool;
25use std::collections::{BTreeMap, HashMap, HashSet};
26use std::sync::Arc;
27use tokio::sync::RwLock;
28use tracing::{debug, info, trace};
29
30pub struct GraphQLState {
32 pub pool: PgPool,
34 pub schema_cache: Arc<SchemaCache>,
36 pub generated_schema: GeneratedSchema,
38 pub schema: Schema,
43 pub role_schemas: HashMap<(String, bool), Schema>,
59 pub ungranted_schema: Option<Schema>,
72 pub config: SchemaConfig,
74 pub subscription_fields: Vec<SubField>,
76 pub broker: Arc<RwLock<Option<NotifyBroker>>>,
78}
79
80struct NullableFederationEntity;
86
87impl ExtensionFactory for NullableFederationEntity {
88 fn create(&self) -> Arc<dyn Extension> {
89 Arc::new(Self)
90 }
91}
92
93#[async_graphql::async_trait::async_trait]
94impl Extension for NullableFederationEntity {
95 async fn resolve(
96 &self,
97 ctx: &ExtensionContext<'_>,
98 info: ResolveInfo<'_>,
99 next: NextResolve<'_>,
100 ) -> ServerResult<Option<Value>> {
101 let parent_type = info.parent_type;
110 let return_type = info.return_type;
111 match next.run(ctx, info).await {
112 Err(error)
113 if error.message
114 == "internal: invalid value for union \"_Entity\", expected \"FieldValue::WithType\""
115 && parent_type == "[_Entity]"
116 && return_type == "_Entity" =>
117 {
118 Ok(Some(Value::Null))
119 }
120 result => result,
121 }
122 }
123}
124
125fn build_role_schemas(
136 schema_cache: &Arc<SchemaCache>,
137 config: &SchemaConfig,
138) -> Result<HashMap<(String, bool), Schema>, GraphQLError> {
139 if !config.names.has_permissions() {
140 return Ok(HashMap::new());
141 }
142
143 let mut schemas = HashMap::new();
144 for role in config.names.roles() {
145 if role == postrust_auth::hasura::ADMIN_ROLE {
149 continue;
150 }
151
152 let variants: &[bool] = match crate::role::has_backend_only(&config.names, role) {
156 true => &[false, true],
157 false => &[false],
158 };
159
160 for &backend in variants {
161 let view = Arc::new(crate::role::cache_for_role(
162 schema_cache,
163 &config.names,
164 role,
165 backend,
166 ));
167 let mut role_config = config.clone();
168 role_config.role = Some(role.to_string());
169
170 let generated = build_schema(&view, &role_config);
171 let built = build_dynamic_schema(
176 &generated,
177 &view,
178 None,
179 role_config.max_rows,
180 Arc::new(role_config.names.clone()),
181 role_config.subscription_refresh(),
182 Some(role),
183 );
184
185 match built {
186 Ok(schema) => {
187 schemas.insert((role.to_string(), backend), schema);
188 }
189 Err(e) => tracing::error!(
196 "GraphQL schema for role \"{}\" could not be built, \
197 so that role is refused: {}",
198 role,
199 e
200 ),
201 }
202 }
203 }
204
205 tracing::info!("GraphQL schemas built for {} roles", schemas.len());
206 Ok(schemas)
207}
208
209fn build_ungranted_schema(
216 schema_cache: &Arc<SchemaCache>,
217 config: &SchemaConfig,
218) -> Option<Schema> {
219 if !config.names.has_permissions() {
220 return None;
221 }
222 const NOBODY: &str = "\u{0}ungranted";
225 let view = Arc::new(crate::role::cache_for_role(
226 schema_cache,
227 &config.names,
228 NOBODY,
229 false,
230 ));
231 let mut role_config = config.clone();
232 role_config.role = Some(NOBODY.to_string());
233 let generated = build_schema(&view, &role_config);
234 build_dynamic_schema(
235 &generated,
236 &view,
237 None,
238 role_config.max_rows,
239 Arc::new(role_config.names.clone()),
240 role_config.subscription_refresh(),
241 Some(NOBODY),
242 )
243 .map_err(|e| {
244 tracing::error!(
245 "the schema for a role with nothing granted could not be built, so such a role is refused instead: {}",
246 e
247 )
248 })
249 .ok()
250}
251
252impl GraphQLState {
253 pub fn new(
255 pool: PgPool,
256 schema_cache: Arc<SchemaCache>,
257 config: SchemaConfig,
258 ) -> Result<Self, GraphQLError> {
259 let generated_schema = build_schema(&schema_cache, &config);
260 let subscription_fields = if config.enable_subscriptions {
261 generate_subscription_fields(&schema_cache, &generated_schema)
262 } else {
263 Vec::new()
264 };
265 let schema = build_dynamic_schema(
266 &generated_schema,
267 &schema_cache,
268 if config.enable_subscriptions {
269 Some(subscription_fields.as_slice())
270 } else {
271 None
272 },
273 config.max_rows,
274 Arc::new(config.names.clone()),
275 config.subscription_refresh(),
276 None,
277 )?;
278
279 let role_schemas = build_role_schemas(&schema_cache, &config)?;
280 let ungranted_schema = build_ungranted_schema(&schema_cache, &config);
281
282 Ok(Self {
283 pool: pool.clone(),
284 schema_cache,
285 generated_schema,
286 schema,
287 role_schemas,
288 ungranted_schema,
289 config,
290 subscription_fields,
291 broker: Arc::new(RwLock::new(None)),
292 })
293 }
294
295 pub fn schema_for(&self, role: Option<&str>, backend: bool) -> Option<&Schema> {
308 if !self.config.names.has_permissions() {
309 return Some(&self.schema);
310 }
311 match role {
312 None | Some(postrust_auth::hasura::ADMIN_ROLE) => Some(&self.schema),
313 Some(role) => self
314 .role_schemas
315 .get(&(role.to_string(), backend))
316 .or_else(|| self.role_schemas.get(&(role.to_string(), false)))
319 .or(self.ungranted_schema.as_ref()),
320 }
321 }
322
323 #[allow(clippy::needless_update)]
325 pub fn rebuild(&mut self) -> Result<(), GraphQLError> {
326 self.role_schemas = build_role_schemas(&self.schema_cache, &self.config)?;
327 self.ungranted_schema = build_ungranted_schema(&self.schema_cache, &self.config);
328 self.generated_schema = build_schema(&self.schema_cache, &self.config);
329 self.subscription_fields = if self.config.enable_subscriptions {
330 generate_subscription_fields(&self.schema_cache, &self.generated_schema)
331 } else {
332 Vec::new()
333 };
334 self.schema = build_dynamic_schema(
335 &self.generated_schema,
336 &self.schema_cache,
337 if self.config.enable_subscriptions {
338 Some(self.subscription_fields.as_slice())
339 } else {
340 None
341 },
342 self.config.max_rows,
343 Arc::new(self.config.names.clone()),
344 self.config.subscription_refresh(),
345 None,
346 )?;
347 Ok(())
348 }
349
350 pub async fn init_subscriptions(&self) -> Result<(), crate::subscription::BrokerError> {
354 if !self.config.enable_subscriptions {
355 return Ok(());
356 }
357
358 let broker = NotifyBroker::new(self.pool.clone());
359
360 let channels: Vec<String> = self
362 .subscription_fields
363 .iter()
364 .map(|f| f.channel_name())
365 .collect();
366
367 if !channels.is_empty() {
368 broker.start(channels).await?;
369 info!(
370 "Subscription broker started with {} channels",
371 self.subscription_fields.len()
372 );
373 }
374
375 let mut broker_guard = self.broker.write().await;
377 *broker_guard = Some(broker);
378
379 Ok(())
380 }
381
382 pub async fn stop_subscriptions(&self) {
384 let broker_guard = self.broker.read().await;
385 if let Some(broker) = broker_guard.as_ref() {
386 broker.stop().await;
387 }
388 }
389
390 pub async fn get_broker(&self) -> Option<Arc<RwLock<Option<NotifyBroker>>>> {
392 Some(Arc::clone(&self.broker))
393 }
394}
395
396pub async fn graphql_handler(
398 State(state): State<Arc<GraphQLState>>,
399 ctx: GraphQLContext,
400 req: GraphQLRequest,
401) -> GraphQLResponse {
402 let request = req
403 .into_inner()
404 .data(ctx)
405 .data(state.pool.clone())
406 .data(Arc::clone(&state.broker));
407 state.schema.execute(request).await.into()
408}
409
410pub async fn graphql_ws_handler(
415 State(state): State<Arc<GraphQLState>>,
416 protocol: async_graphql_axum::GraphQLProtocol,
417 ws: axum::extract::WebSocketUpgrade,
418) -> impl IntoResponse {
419 let schema = state.schema.clone();
420 let pool = state.pool.clone();
421 let broker = Arc::clone(&state.broker);
422
423 ws.protocols(["graphql-transport-ws", "graphql-ws"])
424 .on_upgrade(move |socket| async move {
425 let mut data = async_graphql::Data::default();
426 data.insert(pool);
427 data.insert(broker);
428
429 async_graphql_axum::GraphQLWebSocket::new(socket, schema, protocol)
430 .with_data(data)
431 .serve()
432 .await
433 })
434}
435
436pub async fn graphql_playground() -> impl axum::response::IntoResponse {
438 axum::response::Html(async_graphql::http::playground_source(
439 async_graphql::http::GraphQLPlaygroundConfig::new("/graphql")
440 .subscription_endpoint("/graphql/ws"),
441 ))
442}
443
444fn column_for<'a>(
451 names: &'a crate::names::NameOverrides,
452 schema: &str,
453 table: &str,
454 field: &'a str,
455) -> &'a str {
456 names.column_source(schema, table, field).unwrap_or(field)
457}
458
459fn table_column_for<'a>(
461 names: &'a crate::names::NameOverrides,
462 table: &postrust_core::schema_cache::Table,
463 field: &'a str,
464) -> &'a str {
465 column_for(names, &table.schema, &table.name, field)
466}
467
468fn rename_projection(
476 table: &postrust_core::schema_cache::Table,
477 alias: &str,
478 names: &crate::names::NameOverrides,
479) -> Option<String> {
480 if !names.renames_columns(&table.schema, &table.name) {
481 return None;
482 }
483 let parts: Vec<String> = table
484 .columns
485 .values()
486 .map(|column| {
487 let exposed = names
488 .column(&table.schema, &table.name, &column.name)
489 .unwrap_or(&column.name);
490 format!(
491 "{}.{} AS {}",
492 postrust_sql::escape_ident(alias),
493 postrust_sql::escape_ident(&column.name),
494 postrust_sql::escape_ident(exposed)
495 )
496 })
497 .collect();
498 match parts.is_empty() {
499 true => None,
500 false => Some(parts.join(", ")),
501 }
502}
503
504fn exposed_column_types(
510 table: &postrust_core::schema_cache::Table,
511 names: &crate::names::NameOverrides,
512) -> HashMap<String, String> {
513 table
514 .columns
515 .values()
516 .map(|column| {
517 let exposed = names
518 .column(&table.schema, &table.name, &column.name)
519 .unwrap_or(&column.name);
520 (exposed.to_string(), column.nominal_type.clone())
521 })
522 .collect()
523}
524
525fn write_type_ref(graphql_type: &crate::types::GraphQLType) -> TypeRef {
531 match graphql_type {
532 crate::types::GraphQLType::List(inner) => TypeRef::named_list(leaf_scalar_name(inner)),
533 other => TypeRef::named(leaf_scalar_name(other)),
534 }
535}
536
537fn leaf_scalar_name(graphql_type: &crate::types::GraphQLType) -> String {
539 match graphql_type {
540 crate::types::GraphQLType::List(inner) => leaf_scalar_name(inner),
541 other => other.to_string(),
542 }
543}
544
545fn build_dynamic_schema(
547 generated: &GeneratedSchema,
548 _schema_cache: &SchemaCache,
549 subscription_fields: Option<&[SubField]>,
550 max_rows: Option<i64>,
551 names: Arc<crate::names::NameOverrides>,
552 subscription_refresh: std::time::Duration,
553 role: Option<&str>,
558) -> Result<Schema, GraphQLError> {
559 let mut object_types: HashMap<String, Object> = HashMap::new();
561
562 for (type_name, obj) in &generated.object_types {
567 if obj.fields.is_empty() {
568 continue;
569 }
570 let relationships = generated
571 .relationship_fields
572 .get(type_name)
573 .map(|r| r.as_slice())
574 .unwrap_or(&[]);
575 let table_obj = create_object_type(
576 obj,
577 relationships,
578 generated.federation_entities.get(type_name),
579 );
580 object_types.insert(type_name.clone(), table_obj);
581 }
582
583 let countable: HashSet<&str> = generated
589 .query_fields
590 .iter()
591 .filter(|field| field.aggregates && !field.is_by_pk)
592 .map(|field| field.type_name.as_str())
593 .collect();
594 for (type_name, obj) in &generated.object_types {
595 if obj.fields.is_empty() && !countable.contains(type_name.as_str()) {
596 continue;
597 }
598 for aggregate_type in create_aggregate_types(&obj.local_name, obj) {
599 object_types.insert(aggregate_type.type_name().to_string(), aggregate_type);
600 }
601 }
602
603 let mutable: HashMap<String, String> = generated
607 .mutation_fields
608 .iter()
609 .filter_map(|f| {
610 let response = f
618 .return_type
619 .trim_matches(|c| c == '[' || c == ']' || c == '!')
620 .strip_suffix("_mutation_response")?;
621 Some((response.to_string(), f.type_name.clone()))
622 })
623 .collect();
624 for (base_name, row_type_name) in mutable {
625 let returning = generated
629 .object_types
630 .get(&row_type_name)
631 .is_some_and(|object| !object.fields.is_empty());
632 object_types.insert(
633 mutation_response_type_name(&base_name),
634 create_mutation_response_type(&base_name, &row_type_name, returning),
635 );
636 }
637
638 let query_root = if generated.enable_federation {
643 "Query"
644 } else {
645 "query_root"
646 };
647 let mutation_root = if generated.enable_federation {
648 "Mutation"
649 } else {
650 "mutation_root"
651 };
652 let subscription_root = if generated.enable_federation {
653 "Subscription"
654 } else {
655 "subscription_root"
656 };
657
658 let relationships = Arc::new(generated.relationship_fields.clone());
661 let type_names: Arc<HashMap<(String, String), String>> = Arc::new(
667 generated
668 .object_types
669 .iter()
670 .map(|(type_name, object)| {
671 (
672 (object.table.schema.clone(), object.table.name.clone()),
673 type_name.clone(),
674 )
675 })
676 .collect(),
677 );
678 let query = add_function_fields(
679 create_query_type(
680 generated,
681 query_root,
682 max_rows,
683 Arc::clone(&relationships),
684 Arc::clone(&names),
685 ),
686 generated,
687 false,
688 max_rows,
689 Arc::clone(&relationships),
690 Arc::clone(&names),
691 );
692
693 let mutation = if !generated.mutation_fields.is_empty()
695 || generated.function_fields.iter().any(|f| f.volatile)
696 {
697 Some(add_function_fields(
698 create_mutation_type(
699 generated,
700 mutation_root,
701 Arc::clone(&relationships),
702 Arc::clone(&type_names),
703 Arc::clone(&names),
704 max_rows,
705 role,
706 ),
707 generated,
708 true,
709 max_rows,
710 Arc::clone(&relationships),
711 Arc::clone(&names),
712 ))
713 } else {
714 None
715 };
716
717 let subscription = subscription_fields.map(|_| {
719 create_subscription_type(
720 generated,
721 subscription_root,
722 max_rows,
723 Arc::clone(&relationships),
724 Arc::clone(&names),
725 subscription_refresh,
726 )
727 });
728
729 let mut builder = Schema::build(
731 query_root,
732 mutation.as_ref().map(|_| mutation_root),
733 subscription.as_ref().map(|_| subscription_root),
734 );
735 if generated.enable_federation {
736 builder = builder
737 .enable_federation()
738 .extension(NullableFederationEntity);
739 let entity_state = Arc::new(EntityResolverState::new(
740 generated,
741 Arc::clone(&relationships),
742 Arc::clone(&names),
743 max_rows,
744 ));
745 builder = builder.entity_resolver(move |ctx| {
746 let state = Arc::clone(&entity_state);
747 FieldFuture::new(async move { resolve_entities(&ctx, state.as_ref()).await })
748 });
749 }
750
751 for (_, obj) in object_types {
753 builder = builder.register(obj);
754 }
755
756 builder = builder.register(query);
758
759 if let Some(mutation) = mutation {
761 builder = builder.register(mutation);
762 }
763
764 if let Some(subscription) = subscription {
766 builder = builder.register(subscription);
767 }
768
769 let (bool_exp_inputs, bool_exp_scalars) = crate::input::bool_exp::build_inputs(
776 &generated.object_types,
777 &generated.relationship_fields,
778 );
779 for input in bool_exp_inputs {
780 builder = builder.register(input);
781 }
782
783 let mut scalar_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
784 for object in generated.object_types.values() {
785 for field in object.fields.iter().chain(&object.writable_fields) {
791 scalar_names.insert(leaf_scalar_name(&field.graphql_type));
792 }
793 }
794 scalar_names.insert("JSON".to_string());
797 for function in &generated.function_fields {
800 for (_, pg_type, _) in &function.arguments {
801 scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
802 }
803 }
804 for object in generated.object_types.values() {
807 for field in &object.fields {
808 for (_, pg_type, _) in &field.arguments {
809 scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
810 }
811 }
812 }
813 for relationships in generated.relationship_fields.values() {
814 for relationship in relationships {
815 for (_, pg_type, _) in &relationship.arguments {
816 scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
817 }
818 }
819 }
820
821 scalar_names.extend(bool_exp_scalars);
827
828 for name in scalar_names {
829 if matches!(name.as_str(), "Int" | "Float" | "String" | "Boolean" | "ID") {
830 continue;
831 }
832 if generated.enum_types.contains_key(&name) {
836 continue;
837 }
838 builder = builder
839 .register(Scalar::new(&name).description(format!("The PostgreSQL `{}` type.", name)));
840 }
841
842 let mut key_inputs: HashMap<String, InputObject> = HashMap::new();
850 for field in &generated.mutation_fields {
851 if field.mutation_type != MutationType::UpdateByPk || field.pk_columns.is_empty() {
852 continue;
853 }
854 let base_name = field.local_type_name.as_str();
855 let type_name = format!("{}_pk_columns_input", base_name);
856 let mut input = InputObject::new(&type_name)
857 .description(format!("The primary key of one {} row.", base_name));
858 for (column, pg_type) in &field.pk_columns {
859 input = input.field(InputValue::new(
860 names
861 .column(&field.schema_name, &field.table_name, column)
862 .unwrap_or(column),
863 TypeRef::named_nn(exposed_pk_argument_type(
864 names.as_ref(),
865 &field.schema_name,
866 &field.table_name,
867 column,
868 pg_type,
869 )),
870 ));
871 }
872 key_inputs.insert(type_name, input);
873 }
874 for (_, input) in key_inputs {
875 builder = builder.register(input);
876 }
877
878 let insertable_types: HashSet<&str> = generated
899 .object_types
900 .iter()
901 .filter(|(_, object)| crate::input::mutation::is_insertable(&object.table))
902 .map(|(type_name, _)| type_name.as_str())
903 .collect();
904
905 for (type_name, object) in &generated.object_types {
906 let local_type_name = object.local_name.as_str();
907 let table = &object.table;
908 let writable_named = |allowed: Option<&crate::names::ColumnSet>| {
916 object
917 .writable_fields
918 .iter()
919 .filter(|field| {
920 let column = table_column_for(&names, table, &field.name);
921 table.get_column(column).is_some()
922 && allowed.is_none_or(|set| set.allows(column))
923 })
924 .collect::<Vec<&crate::schema::object::GraphQLField>>()
925 };
926 let writable = writable_named(None);
927 let insert_fields = write_fields(object, &names, role, crate::role::Verb::Insert);
928 let update_fields = write_fields(object, &names, role, crate::role::Verb::Update);
929 if writable.is_empty() {
930 continue;
931 }
932
933 let relationships = generated
934 .relationship_fields
935 .get(type_name)
936 .map(|r| r.as_slice())
937 .unwrap_or(&[]);
938 let conflict_type = match table.unique_constraints.is_empty() {
939 true => None,
940 false => Some(format!("{}_on_conflict", local_type_name)),
941 };
942
943 if crate::input::mutation::is_insertable(table) {
944 let mut insert = InputObject::new(format!("{}_insert_input", local_type_name))
945 .description(format!("The columns of a new {} row.", local_type_name));
946 let mut taken: HashSet<&str> = HashSet::new();
947 for field in &insert_fields {
948 taken.insert(field.name.as_str());
952 insert = insert.field(InputValue::new(
953 &field.name,
954 write_type_ref(&field.graphql_type),
955 ));
956 }
957 for relationship in relationships {
961 if !insertable_types.contains(relationship.target_type.as_str()) {
962 continue;
963 }
964 if !taken.insert(relationship.name.as_str()) {
965 continue;
966 }
967 let suffix = match relationship.is_list {
968 true => "arr_rel_insert_input",
969 false => "obj_rel_insert_input",
970 };
971 insert = insert.field(InputValue::new(
972 &relationship.name,
973 TypeRef::named(format!("{}_{}", relationship.target_local_name, suffix)),
974 ));
975 }
976 builder = builder.register(insert);
977
978 let data_type = format!("{}_insert_input", local_type_name);
982 let mut object_rel =
983 InputObject::new(format!("{}_obj_rel_insert_input", local_type_name))
984 .description(format!(
985 "One {} row written beside its parent.",
986 local_type_name
987 ))
988 .field(InputValue::new("data", TypeRef::named_nn(&data_type)));
989 let mut array_rel =
990 InputObject::new(format!("{}_arr_rel_insert_input", local_type_name))
991 .description(format!(
992 "{} rows written beside their parent.",
993 local_type_name
994 ))
995 .field(InputValue::new(
996 "data",
997 TypeRef::named_nn_list_nn(&data_type),
998 ));
999 if let Some(conflict) = &conflict_type {
1000 object_rel =
1001 object_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1002 array_rel =
1003 array_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1004 }
1005 builder = builder.register(object_rel).register(array_rel);
1006 }
1007
1008 if crate::input::mutation::is_updatable(table) {
1009 let mut set = InputObject::new(format!("{}_set_input", local_type_name))
1010 .description(format!("Columns of {} to replace.", local_type_name));
1011 let mut numeric = InputObject::new(format!("{}_inc_input", local_type_name))
1012 .description(format!("Columns of {} to add to.", local_type_name));
1013 let mut any_numeric = false;
1014 let mut any_jsonb = false;
1015 for field in &update_fields {
1016 if matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1017 any_jsonb = true;
1018 }
1019 set = set.field(InputValue::new(
1020 &field.name,
1021 write_type_ref(&field.graphql_type),
1022 ));
1023 if crate::schema::aggregate::is_incrementable(&field.graphql_type) {
1026 any_numeric = true;
1027 numeric = numeric.field(InputValue::new(
1028 &field.name,
1029 TypeRef::named(leaf_scalar_name(&field.graphql_type)),
1030 ));
1031 }
1032 }
1033 builder = builder.register(set);
1034 if any_numeric {
1036 builder = builder.register(numeric);
1037 }
1038
1039 let mut updates = InputObject::new(format!("{}_updates", local_type_name))
1044 .description(format!(
1045 "One update to {}: which rows, and what to write.",
1046 local_type_name
1047 ))
1048 .field(InputValue::new(
1049 "where",
1050 TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1051 ))
1052 .field(InputValue::new(
1053 "_set",
1054 TypeRef::named(format!("{}_set_input", local_type_name)),
1055 ));
1056 if any_numeric {
1057 updates = updates.field(InputValue::new(
1058 "_inc",
1059 TypeRef::named(format!("{}_inc_input", local_type_name)),
1060 ));
1061 }
1062 for (operator, _) in JSONB_OPERATORS {
1069 if !any_jsonb {
1070 break;
1071 }
1072 updates = updates.field(InputValue::new(
1073 *operator,
1074 TypeRef::named(jsonb_operator_input(local_type_name, operator)),
1075 ));
1076 }
1077 builder = builder.register(updates);
1078 if any_jsonb {
1079 for (operator, item) in JSONB_OPERATORS {
1080 let mut input =
1081 InputObject::new(jsonb_operator_input(local_type_name, operator))
1082 .description(format!(
1083 "Columns of {} to apply `{}` to.",
1084 local_type_name, operator
1085 ));
1086 for field in &update_fields {
1087 if !matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1088 continue;
1089 }
1090 input = input.field(InputValue::new(&field.name, item()));
1091 }
1092 builder = builder.register(input);
1093 }
1094 }
1095 }
1096 }
1097
1098 for function in &generated.function_fields {
1101 if function.arguments.is_empty() {
1102 continue;
1103 }
1104 let mut args = InputObject::new(format!("{}_args", function.name))
1105 .description(format!("Arguments to {}.", function.name));
1106 for (name, pg_type, _) in &function.arguments {
1107 let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1108 args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1116 }
1117 builder = builder.register(args);
1118 }
1119
1120 for (type_name, relationships) in &generated.relationship_fields {
1125 let local_type_name = generated
1126 .object_types
1127 .get(type_name)
1128 .map(|object| object.local_name.as_str())
1129 .unwrap_or(type_name);
1130 for relationship in relationships {
1131 if relationship.arguments.is_empty() {
1132 continue;
1133 }
1134 let mut args =
1135 InputObject::new(computed_args_type_name(local_type_name, &relationship.name))
1136 .description(format!(
1137 "Arguments to {}, beside the row it is asked of.",
1138 relationship.name
1139 ));
1140 for (name, pg_type, _) in &relationship.arguments {
1141 let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1142 args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1144 }
1145 builder = builder.register(args);
1146 }
1147 }
1148
1149 for object in generated.object_types.values() {
1154 let local_type_name = object.local_name.as_str();
1155 for field in &object.fields {
1156 if field.arguments.is_empty() {
1157 continue;
1158 }
1159 let mut args = InputObject::new(computed_args_type_name(local_type_name, &field.name))
1160 .description(format!(
1161 "Arguments to {}, beside the row it is asked of.",
1162 field.name
1163 ));
1164 for (name, pg_type, _) in &field.arguments {
1165 let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1166 args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1168 }
1169 builder = builder.register(args);
1170 }
1171 }
1172
1173 {
1177 use crate::input::bool_exp as be;
1178 let mut targets: BTreeMap<&str, &str> = BTreeMap::new();
1179 for relationships in generated.relationship_fields.values() {
1180 for relationship in relationships {
1181 if relationship.is_list {
1182 targets.insert(
1183 relationship.target_local_name.as_str(),
1184 relationship.target_type.as_str(),
1185 );
1186 }
1187 }
1188 }
1189 for (target, target_type) in targets {
1190 let Some(object) = generated.object_types.get(target_type) else {
1191 continue;
1192 };
1193 let booleans: Vec<&str> = object
1197 .fields
1198 .iter()
1199 .filter(|field| {
1200 object.table.get_column(&field.name).is_some()
1201 && matches!(field.graphql_type, crate::types::GraphQLType::Boolean)
1202 })
1203 .map(|field| field.name.as_str())
1204 .collect();
1205
1206 let mut over = InputObject::new(be::aggregate_bool_exp_type_name(target)).description(
1207 format!("A question about the whole set of related {} rows.", target),
1208 );
1209 for function in be::AGGREGATE_PREDICATES {
1210 if function != "count" && booleans.is_empty() {
1211 continue;
1212 }
1213 over = over.field(InputValue::new(
1214 function,
1215 TypeRef::named(be::aggregate_bool_exp_function(target, function)),
1216 ));
1217
1218 let (arguments, predicate) = match function {
1221 "count" => (
1222 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1223 target,
1224 )),
1225 be::comparison_type_name("Int"),
1226 ),
1227 _ => (
1228 TypeRef::named_nn(be::aggregate_bool_exp_columns(target, function)),
1229 be::comparison_type_name("Boolean"),
1230 ),
1231 };
1232 let mut input = InputObject::new(be::aggregate_bool_exp_function(target, function))
1233 .description(format!(
1234 "`{}` over the related {} rows, and what it has to be.",
1235 function, target
1236 ))
1237 .field(InputValue::new("arguments", arguments))
1238 .field(InputValue::new("distinct", TypeRef::named("Boolean")))
1239 .field(InputValue::new(
1240 "filter",
1241 TypeRef::named(be::bool_exp_type_name(target)),
1242 ))
1243 .field(InputValue::new("predicate", TypeRef::named_nn(predicate)));
1244 if function == "count" {
1245 input = input.description(format!(
1246 "How many related {} rows there have to be.",
1247 target
1248 ));
1249 }
1250 builder = builder.register(input);
1251
1252 if function != "count" {
1253 let mut columns = Enum::new(be::aggregate_bool_exp_columns(target, function))
1254 .description(format!("A boolean column of {}.", target));
1255 for name in &booleans {
1256 columns = columns.item(EnumItem::new(*name));
1257 }
1258 builder = builder.register(columns);
1259 }
1260 }
1261 builder = builder.register(over);
1262 }
1263 }
1264
1265 for object in generated.object_types.values() {
1269 let local_type_name = object.local_name.as_str();
1270 let updatable_columns = write_fields(object, &names, role, crate::role::Verb::Update);
1276 if object.table.unique_constraints.is_empty() {
1277 continue;
1278 }
1279
1280 let mut constraints =
1281 Enum::new(format!("{}_constraint", local_type_name)).description(format!(
1282 "A uniqueness of {} that an insert may conflict with.",
1283 local_type_name
1284 ));
1285 for (name, columns) in &object.table.unique_constraints {
1286 constraints = constraints
1287 .item(EnumItem::new(name).description(format!("unique ({})", columns.join(", "))));
1288 }
1289
1290 let mut updatable = Enum::new(format!("{}_update_column", local_type_name)).description(
1291 format!("A column of {} an upsert may write.", local_type_name),
1292 );
1293 for field in &updatable_columns {
1294 updatable = updatable.item(EnumItem::new(&field.name));
1295 }
1296 if updatable_columns.is_empty() {
1305 updatable = updatable.item(
1306 EnumItem::new(PLACEHOLDER_COLUMN)
1307 .description("No column may be written by this upsert."),
1308 );
1309 }
1310
1311 let on_conflict = InputObject::new(format!("{}_on_conflict", local_type_name))
1312 .description(format!(
1313 "What to do when an insert into {} conflicts.",
1314 local_type_name
1315 ))
1316 .field(InputValue::new(
1317 "constraint",
1318 TypeRef::named_nn(format!("{}_constraint", local_type_name)),
1319 ))
1320 .field(
1325 InputValue::new(
1326 "update_columns",
1327 TypeRef::named_nn_list_nn(format!("{}_update_column", local_type_name)),
1328 )
1329 .default_value(Value::List(Vec::new())),
1330 )
1331 .field(InputValue::new(
1332 "where",
1333 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1334 ));
1335
1336 builder = builder
1337 .register(constraints)
1338 .register(updatable)
1339 .register(on_conflict);
1340 }
1341
1342 for (type_name, members) in &generated.enum_types {
1344 let mut generated_enum =
1345 Enum::new(type_name).description(format!("The values {} allows.", type_name));
1346 for (value, comment) in members {
1347 let item = EnumItem::new(value);
1348 generated_enum = generated_enum.item(match comment {
1349 Some(description) => item.description(description),
1350 None => item,
1351 });
1352 }
1353 builder = builder.register(generated_enum);
1354 }
1355
1356 let (order_inputs, order_enums) = crate::input::order_by::build_inputs(
1359 &generated.object_types,
1360 &generated.relationship_fields,
1361 );
1362 for input in order_inputs {
1363 builder = builder.register(input);
1364 }
1365 for enum_type in order_enums {
1366 builder = builder.register(enum_type);
1367 }
1368
1369 builder
1370 .finish()
1371 .map_err(|e| GraphQLError::SchemaError(e.to_string()))
1372}
1373
1374const PLACEHOLDER_COLUMN: &str = "_PLACEHOLDER";
1381
1382pub fn mutation_response_type_name(base_name: &str) -> String {
1384 format!("{}_mutation_response", base_name)
1385}
1386
1387fn create_mutation_response_type(base_name: &str, row_type: &str, returning: bool) -> Object {
1394 let response_name = mutation_response_type_name(base_name);
1395
1396 let response = Object::new(&response_name)
1397 .description(format!("The rows {} changed, and how many.", base_name))
1398 .field(Field::new(
1399 "affected_rows",
1400 TypeRef::named_nn(TypeRef::INT),
1401 |ctx| {
1402 FieldFuture::new(async move {
1403 if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1404 if let Some(value) = map.get(&async_graphql::Name::new("affected_rows")) {
1405 return Ok(Some(FieldValue::value(value.clone())));
1406 }
1407 }
1408 Ok(Some(FieldValue::value(Value::from(0))))
1409 })
1410 },
1411 ));
1412 if !returning {
1416 return response;
1417 }
1418 response.field(Field::new(
1419 "returning",
1420 TypeRef::named_nn_list_nn(row_type.to_string()),
1421 |ctx| {
1422 FieldFuture::new(async move {
1423 let rows = match ctx.parent_value.as_value() {
1424 Some(Value::Object(map)) => {
1425 match map.get(&async_graphql::Name::new("returning")) {
1426 Some(Value::List(items)) => items.clone(),
1427 _ => Vec::new(),
1428 }
1429 }
1430 _ => Vec::new(),
1431 };
1432 Ok(Some(FieldValue::list(
1433 rows.into_iter().map(FieldValue::value),
1434 )))
1435 })
1436 },
1437 ))
1438}
1439
1440fn create_aggregate_types(base_name: &str, object: &TableObjectType) -> Vec<Object> {
1446 use crate::schema::aggregate as agg;
1447
1448 let mut types = Vec::new();
1449
1450 let fields_type = agg::aggregate_fields_type_name(base_name);
1452 let has_rows = !object.fields.is_empty();
1456 let mut over = Object::new(agg::aggregate_type_name(base_name))
1457 .description(match has_rows {
1458 true => format!("Aggregates over {}, with the rows themselves.", base_name),
1459 false => format!("Aggregates over {}.", base_name),
1460 })
1461 .field(Field::new(
1462 "aggregate",
1463 TypeRef::named(&fields_type),
1464 |ctx| FieldFuture::new(async move { Ok(child_of(&ctx, "aggregate")) }),
1465 ));
1466 if has_rows {
1467 over = over.field(Field::new(
1468 "nodes",
1469 TypeRef::named_nn_list_nn(object.name.clone()),
1470 |ctx| {
1471 FieldFuture::new(async move {
1472 let rows = match child_value(&ctx, "nodes") {
1473 Some(Value::List(items)) => items,
1474 _ => Vec::new(),
1475 };
1476 Ok(Some(FieldValue::list(
1477 rows.into_iter().map(FieldValue::value),
1478 )))
1479 })
1480 },
1481 ));
1482 }
1483 types.push(over);
1484
1485 let mut count = Field::new("count", TypeRef::named_nn(TypeRef::INT), |ctx| {
1487 let key = ctx.ctx.field().alias().unwrap_or("count").to_string();
1492 FieldFuture::new(async move {
1493 Ok(Some(FieldValue::value(
1494 child_value(&ctx, &key).unwrap_or(Value::from(0)),
1495 )))
1496 })
1497 });
1498 if has_rows {
1504 count = count
1505 .argument(InputValue::new(
1506 "columns",
1507 TypeRef::named_nn_list(agg_select_column(base_name)),
1508 ))
1509 .argument(InputValue::new("distinct", TypeRef::named("Boolean")));
1510 }
1511 let mut aggregate_fields = Object::new(&fields_type)
1512 .description(format!("Aggregate functions over {}.", base_name))
1513 .field(count);
1514
1515 for (function, returns, columns) in agg::functions_for(object) {
1516 let function_type = agg::function_fields_type_name(base_name, function);
1517 let mut per_column = Object::new(&function_type).description(format!(
1518 "`{}` of each {} column it applies to.",
1519 function, base_name
1520 ));
1521 for column in &columns {
1522 let column_name = column.clone();
1523 let type_name = agg::field_type_for(object, column, returns);
1524 per_column =
1525 per_column.field(Field::new(column, TypeRef::named(type_name), move |ctx| {
1526 let column_name = column_name.clone();
1527 FieldFuture::new(async move {
1528 Ok(child_value(&ctx, &column_name).map(FieldValue::value))
1529 })
1530 }));
1531 }
1532 types.push(per_column);
1533
1534 let owned = function.to_string();
1535 aggregate_fields = aggregate_fields.field(Field::new(
1536 function,
1537 TypeRef::named(&function_type),
1538 move |ctx| {
1539 let owned = owned.clone();
1540 FieldFuture::new(async move { Ok(child_of(&ctx, &owned)) })
1541 },
1542 ));
1543 }
1544
1545 types.push(aggregate_fields);
1546 types
1547}
1548
1549fn agg_select_column(base_name: &str) -> String {
1551 crate::input::order_by::select_column_type_name(base_name)
1552}
1553
1554fn child_value(ctx: &ResolverContext<'_>, key: &str) -> Option<Value> {
1556 match ctx.parent_value.as_value() {
1557 Some(Value::Object(map)) => map.get(&async_graphql::Name::new(key)).cloned(),
1558 _ => None,
1559 }
1560}
1561
1562fn child_of<'a>(ctx: &ResolverContext<'_>, key: &str) -> Option<FieldValue<'a>> {
1567 match child_value(ctx, key) {
1568 None | Some(Value::Null) => None,
1569 Some(value) => Some(FieldValue::value(value)),
1570 }
1571}
1572
1573fn coded_error(code: &'static str, message: impl Into<String>) -> async_graphql::Error {
1579 let mut error = async_graphql::Error::new(message);
1580 let mut extensions = async_graphql::ErrorExtensionValues::default();
1581 extensions.set("code", code);
1582 error.extensions = Some(extensions);
1583 error
1584}
1585
1586fn validation_error(message: impl Into<String>) -> async_graphql::Error {
1588 coded_error("validation-failed", message)
1589}
1590
1591fn database_error(error: sqlx::Error) -> async_graphql::Error {
1603 let sqlx::Error::Database(db) = &error else {
1604 return async_graphql::Error::new(error.to_string());
1605 };
1606 let described = match db.code().as_deref() {
1607 Some("23505") => "Uniqueness violation. ",
1610 Some("23502") => "Not-NULL violation. ",
1611 Some("23503") => "Foreign key violation. ",
1612 Some("23514") => "Check constraint violation. ",
1613 _ => "",
1617 };
1618 let error = async_graphql::Error::new(format!("{}{}", described, db.message()));
1619 let coded = match db.code().as_deref() {
1624 Some(code) if code.starts_with("23") => Some("constraint-violation"),
1625 Some("22025") => Some("bad-request"),
1629 Some(code) if code.starts_with("22") => Some("data-exception"),
1631 _ => None,
1632 };
1633 match coded {
1634 Some(code) => at_code(error, code),
1635 None => error,
1636 }
1637}
1638
1639fn at_code(mut error: async_graphql::Error, code: &'static str) -> async_graphql::Error {
1641 let mut extensions = error.extensions.take().unwrap_or_default();
1642 extensions.set("code", code);
1643 error.extensions = Some(extensions);
1644 error
1645}
1646
1647fn at_path(mut error: async_graphql::Error, path: &str) -> async_graphql::Error {
1656 let mut extensions = error.extensions.take().unwrap_or_default();
1657 extensions.set("path", path);
1658 error.extensions = Some(extensions);
1659 error
1660}
1661
1662fn create_object_type(
1663 obj: &TableObjectType,
1664 relationships: &[RelationshipField],
1665 federation_entity: Option<&FederationEntity>,
1666) -> Object {
1667 let mut object = Object::new(&obj.name);
1668 let entity_is_shared = federation_entity.is_some_and(|entity| entity.shared);
1669 if let Some(entity) = federation_entity {
1670 object = object.key(entity.key_fields_sdl());
1671 }
1672
1673 object = match obj.description() {
1677 Some("") => object,
1680 Some(desc) => object.description(desc),
1681 None => object.description(format!(
1682 "columns and relationships of \"{}\"",
1683 obj.table_name()
1684 )),
1685 };
1686
1687 let mut taken: HashSet<String> = obj.fields.iter().map(|f| f.name.clone()).collect();
1700
1701 let mut fields: Vec<(String, Field)> = Vec::new();
1705
1706 for field in &obj.fields {
1707 let field_name = field.name.clone();
1708 let field_type = graphql_type_ref(&field.type_string());
1709 let takes_path = matches!(&field.graphql_type, crate::types::GraphQLType::Json)
1715 || matches!(
1716 &field.graphql_type,
1717 crate::types::GraphQLType::Custom(name) if name == "json"
1718 );
1719
1720 let gql_field = Field::new(&field.name, field_type, move |ctx| {
1724 let field_name = field_name.clone();
1725 FieldFuture::new(async move {
1726 if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1728 let key = async_graphql::Name::new(&field_name);
1730 let path = match takes_path {
1731 true => ctx.args.get("path").and_then(|v| v.string().ok()),
1732 false => None,
1733 };
1734 match map.get(&key) {
1735 Some(Value::Null) => return Ok(None),
1740 Some(val) => {
1741 let val = match path {
1742 Some(path) => walk_json_path(val, path)?,
1743 None => val.clone(),
1744 };
1745 return match val {
1746 Value::Null => Ok(None),
1747 val => Ok(Some(FieldValue::value(val))),
1748 };
1749 }
1750 None => {}
1751 }
1752 }
1753
1754 Ok(None)
1756 })
1757 });
1758
1759 let gql_field = match takes_path {
1760 true => gql_field.argument(
1761 InputValue::new("path", TypeRef::named("String")).description("JSON select path"),
1762 ),
1763 false => gql_field,
1764 };
1765
1766 let gql_field = match field.arguments.is_empty() {
1770 true => gql_field,
1771 false => gql_field.argument(InputValue::new(
1772 "args",
1773 TypeRef::named_nn(computed_args_type_name(&obj.local_name, &field.name)),
1774 )),
1775 };
1776
1777 let gql_field = if let Some(desc) = &field.description {
1778 gql_field.description(desc)
1779 } else {
1780 gql_field
1781 };
1782 let gql_field = if entity_is_shared && !field.is_pk {
1783 gql_field.shareable()
1784 } else {
1785 gql_field
1786 };
1787
1788 fields.push((field.name.clone(), gql_field));
1789 }
1790
1791 for rel in relationships {
1795 if !taken.insert(rel.name.clone()) {
1796 tracing::warn!(
1797 "{}: not exposing the relationship to \"{}\" as `{}` -- the table \
1798 already has a column of that name",
1799 obj.name,
1800 rel.target_type,
1801 rel.name
1802 );
1803 continue;
1804 }
1805 let field_name = rel.name.clone();
1806 let field_type = if rel.is_list {
1807 TypeRef::named_nn_list_nn(&rel.target_type)
1808 } else if always_present(rel, &obj.table) {
1809 TypeRef::named_nn(&rel.target_type)
1810 } else {
1811 TypeRef::named(&rel.target_type)
1812 };
1813
1814 let is_list = rel.is_list;
1815 let mut gql_field = Field::new(&rel.name, field_type, move |ctx| {
1816 let field_name = field_name.clone();
1817 FieldFuture::new(async move {
1818 if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1819 let key = async_graphql::Name::new(&field_name);
1820 match map.get(&key) {
1821 Some(Value::Null) if !is_list => return Ok(None),
1826 Some(val) => return Ok(Some(FieldValue::value(val.clone()))),
1827 None => {}
1828 }
1829 }
1830 Ok(None)
1831 })
1832 });
1833
1834 if !rel.arguments.is_empty() {
1845 gql_field = gql_field.argument(InputValue::new(
1846 "args",
1847 TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1848 ));
1849 }
1850 if rel.is_list {
1851 gql_field = gql_field
1852 .argument(InputValue::new(
1853 "distinct_on",
1854 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1855 &rel.target_local_name,
1856 )),
1857 ))
1858 .argument(InputValue::new(
1859 "where",
1860 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1861 &rel.target_local_name,
1862 )),
1863 ))
1864 .argument(InputValue::new(
1865 "order_by",
1866 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1867 &rel.target_local_name,
1868 )),
1869 ))
1870 .argument(InputValue::new("limit", TypeRef::named("Int")))
1871 .argument(InputValue::new("offset", TypeRef::named("Int")));
1872 }
1873
1874 let gql_field = if let Some(desc) = &rel.description {
1875 gql_field.description(desc)
1876 } else {
1877 gql_field
1878 };
1879 let gql_field = if entity_is_shared {
1880 gql_field.shareable()
1881 } else {
1882 gql_field
1883 };
1884
1885 fields.push((rel.name.clone(), gql_field));
1886
1887 if rel.is_list {
1892 let aggregate_field = format!("{}_aggregate", rel.name);
1893 if taken.insert(aggregate_field.clone()) {
1894 let key = aggregate_field.clone();
1895 fields.push((
1896 aggregate_field.clone(),
1897 Field::new(
1898 &aggregate_field,
1899 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
1900 &rel.target_local_name,
1901 )),
1902 move |ctx| {
1903 let key = key.clone();
1904 FieldFuture::new(async move {
1905 Ok(Some(FieldValue::value(
1909 child_value(&ctx, &key).unwrap_or(Value::Null),
1910 )))
1911 })
1912 },
1913 )
1914 .argument(InputValue::new(
1919 "distinct_on",
1920 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1921 &rel.target_local_name,
1922 )),
1923 ))
1924 .argument(InputValue::new(
1925 "where",
1926 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1927 &rel.target_local_name,
1928 )),
1929 ))
1930 .argument(InputValue::new(
1931 "order_by",
1932 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1933 &rel.target_local_name,
1934 )),
1935 ))
1936 .argument(InputValue::new("limit", TypeRef::named("Int")))
1937 .argument(InputValue::new("offset", TypeRef::named("Int")))
1938 .description(format!("Aggregates over {}.", rel.name)),
1939 ));
1940 if !rel.arguments.is_empty() {
1943 let last = fields.len() - 1;
1944 let (name, field) = fields.remove(last);
1945 fields.push((
1946 name,
1947 field.argument(InputValue::new(
1948 "args",
1949 TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1950 )),
1951 ));
1952 }
1953 if entity_is_shared {
1954 let last = fields.len() - 1;
1955 let (name, field) = fields.remove(last);
1956 fields.push((name, field.shareable()));
1957 }
1958 }
1959 }
1960 }
1961
1962 fields.sort_by(|(a, _), (b, _)| a.cmp(b));
1963 for (_, field) in fields {
1964 object = object.field(field);
1965 }
1966
1967 object
1968}
1969
1970trait RootField: Sized {
1977 fn with_argument(self, value: InputValue) -> Self;
1978}
1979
1980impl RootField for Field {
1981 fn with_argument(self, value: InputValue) -> Self {
1982 self.argument(value)
1983 }
1984}
1985
1986impl RootField for SubscriptionField {
1987 fn with_argument(self, value: InputValue) -> Self {
1988 self.argument(value)
1989 }
1990}
1991
1992fn with_row_arguments<F: RootField>(field: F, type_name: &str) -> F {
1995 with_row_arguments_named(field, type_name, true)
1996}
1997
1998fn with_row_arguments_named<F: RootField>(field: F, type_name: &str, columns: bool) -> F {
2006 let mut field = field;
2007 if columns {
2008 field = field.with_argument(InputValue::new(
2009 "distinct_on",
2010 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(type_name)),
2011 ));
2012 }
2013 field = field
2014 .with_argument(InputValue::new("limit", TypeRef::named("Int")))
2015 .with_argument(InputValue::new("offset", TypeRef::named("Int")));
2016 if columns {
2017 field = field.with_argument(InputValue::new(
2018 "order_by",
2019 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(type_name)),
2020 ));
2021 }
2022 field.with_argument(InputValue::new(
2023 "where",
2024 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(type_name)),
2025 ))
2026}
2027
2028fn with_key_arguments<F: RootField>(
2031 field: F,
2032 schema_name: &str,
2033 table_name: &str,
2034 pk_columns: &[(String, String)],
2035 names: &crate::names::NameOverrides,
2036) -> F {
2037 let mut field = field;
2038 for (column, pg_type) in pk_columns {
2039 field = field.with_argument(InputValue::new(
2040 names
2041 .column(schema_name, table_name, column)
2042 .unwrap_or(column),
2043 TypeRef::named_nn(exposed_pk_argument_type(
2044 names,
2045 schema_name,
2046 table_name,
2047 column,
2048 pg_type,
2049 )),
2050 ));
2051 }
2052 field
2053}
2054
2055fn create_query_type(
2057 generated: &GeneratedSchema,
2058 root_name: &str,
2059 max_rows: Option<i64>,
2060 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2061 names: Arc<crate::names::NameOverrides>,
2062) -> Object {
2063 let mut roots: Vec<(String, Field)> = Vec::new();
2068
2069 for field in &generated.query_fields {
2070 let table_name = field.table_name.clone();
2071 let schema_name = field.schema_name.clone();
2072 let type_name = field.type_name.clone();
2073 let local_type_name = field.local_type_name.clone();
2074 let is_by_pk = field.is_by_pk;
2075 let pk_columns = field.pk_columns.clone();
2076 let return_type = graphql_type_ref(&field.return_type);
2077
2078 let spec_type_name = local_type_name.clone();
2079 let spec = Arc::new(QueryFieldSpec {
2080 schema_name,
2081 table_name,
2082 type_name,
2083 is_by_pk,
2084 pk_columns: pk_columns.clone(),
2085 max_rows,
2086 relationships: Arc::clone(&relationships),
2087 names: Arc::clone(&names),
2088 call: None,
2089 });
2090
2091 let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2092 let spec = Arc::clone(&spec);
2093 FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
2094 });
2095
2096 gql_field = match is_by_pk {
2098 false => with_row_arguments(gql_field, &spec_type_name),
2099 true => with_key_arguments(
2100 gql_field,
2101 &field.schema_name,
2102 &field.table_name,
2103 &pk_columns,
2104 names.as_ref(),
2105 ),
2106 };
2107
2108 if let Some(desc) = &field.description {
2109 gql_field = gql_field.description(desc);
2110 }
2111
2112 if field.rows {
2117 roots.push((field.name.clone(), gql_field));
2118 }
2119
2120 if !is_by_pk && field.aggregates {
2128 let agg_spec = Arc::new(AggregateSpec {
2129 schema_name: field.schema_name.clone(),
2130 table_name: field.table_name.clone(),
2131 type_name: field.type_name.clone(),
2132 max_rows,
2133 relationships: Arc::clone(&relationships),
2134 names: Arc::clone(&names),
2135 call: None,
2136 });
2137 let aggregate_field_name = field.aggregate_name.clone().unwrap_or_else(|| {
2138 crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2139 });
2140 let aggregate_field_name_for_sorting = aggregate_field_name.clone();
2141 let mut agg_field = Field::new(
2142 aggregate_field_name,
2143 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2144 &field.local_type_name,
2145 )),
2146 move |ctx| {
2147 let agg_spec = Arc::clone(&agg_spec);
2148 FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
2149 },
2150 );
2151 agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2152 agg_field = match field.aggregate_description.as_deref() {
2153 Some("") => agg_field,
2154 Some(given) => agg_field.description(given),
2155 None => agg_field.description(format!(
2156 "fetch aggregated fields from the table: \"{}\"",
2157 field.table_name
2158 )),
2159 };
2160 roots.push((aggregate_field_name_for_sorting, agg_field));
2161 }
2162 }
2163
2164 roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2165 let mut query = Object::new(root_name);
2166 if roots.is_empty() {
2170 return query.field(
2171 Field::new("no_queries_available", TypeRef::named("String"), |_| {
2172 FieldFuture::new(async move { Ok(None::<Value>) })
2173 })
2174 .description("There are no queries available to the current role."),
2175 );
2176 }
2177 for (_, field) in roots {
2178 query = query.field(field);
2179 }
2180
2181 query
2182}
2183
2184type JsonbOperator = (&'static str, fn() -> TypeRef);
2186
2187const JSONB_OPERATORS: &[JsonbOperator] = &[
2194 ("_append", || TypeRef::named("jsonb")),
2195 ("_delete_at_path", || TypeRef::named_nn_list("String")),
2196 ("_delete_elem", || TypeRef::named("Int")),
2197 ("_delete_key", || TypeRef::named("String")),
2198 ("_prepend", || TypeRef::named("jsonb")),
2199];
2200
2201fn jsonb_operator_input(type_name: &str, operator: &str) -> String {
2203 format!("{}{}_input", type_name, operator)
2204}
2205
2206fn with_update_operators(
2213 field: Field,
2214 base_name: &str,
2215 has_numeric: bool,
2216 has_jsonb: bool,
2217) -> Field {
2218 let mut field = field;
2219 if has_jsonb {
2222 for (operator, _) in JSONB_OPERATORS {
2223 field = field.argument(InputValue::new(
2224 *operator,
2225 TypeRef::named(jsonb_operator_input(base_name, operator)),
2226 ));
2227 }
2228 }
2229 if has_numeric {
2230 field = field.argument(InputValue::new(
2231 "_inc",
2232 TypeRef::named(format!("{}_inc_input", base_name)),
2233 ));
2234 }
2235 field.argument(InputValue::new(
2236 "_set",
2237 TypeRef::named(format!("{}_set_input", base_name)),
2238 ))
2239}
2240
2241fn write_fields<'a>(
2251 object: &'a crate::schema::object::TableObjectType,
2252 names: &crate::names::NameOverrides,
2253 role: Option<&str>,
2254 verb: crate::role::Verb,
2255) -> Vec<&'a crate::schema::object::GraphQLField> {
2256 let allowed = role.and_then(|role| {
2257 names
2258 .permissions(&object.table.schema, &object.table.name, role)
2259 .and_then(|granted| granted.write_columns(verb))
2260 });
2261 object
2262 .writable_fields
2263 .iter()
2264 .filter(|field| {
2265 let column = table_column_for(names, &object.table, &field.name);
2266 object
2274 .table
2275 .get_column(column)
2276 .is_some_and(|c| !c.always_generated)
2277 && allowed.is_none_or(|set| set.allows(column))
2278 })
2279 .collect()
2280}
2281
2282fn update_inputs(
2284 object: &crate::schema::object::TableObjectType,
2285 names: &crate::names::NameOverrides,
2286 role: Option<&str>,
2287) -> (bool, bool) {
2288 let fields = write_fields(object, names, role, crate::role::Verb::Update);
2289 (
2290 fields
2291 .iter()
2292 .any(|f| crate::schema::aggregate::is_incrementable(&f.graphql_type)),
2293 fields
2294 .iter()
2295 .any(|f| matches!(&f.graphql_type, crate::types::GraphQLType::Json)),
2296 )
2297}
2298
2299fn create_mutation_type(
2300 generated: &GeneratedSchema,
2301 root_name: &str,
2302 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2303 type_names: Arc<HashMap<(String, String), String>>,
2304 names: Arc<crate::names::NameOverrides>,
2305 max_rows: Option<i64>,
2306 role: Option<&str>,
2307) -> Object {
2308 let mut mutation = Object::new(root_name);
2309 let mut roots: Vec<(String, Field)> = Vec::new();
2311
2312 let mut has_numeric_column: HashSet<String> = HashSet::new();
2319 let mut has_jsonb_column: HashSet<String> = HashSet::new();
2320 for object in generated.object_types.values() {
2321 let (numeric, jsonb) = update_inputs(object, &names, role);
2322 if numeric {
2323 has_numeric_column.insert(object.local_name.clone());
2324 }
2325 if jsonb {
2326 has_jsonb_column.insert(object.local_name.clone());
2327 }
2328 }
2329
2330 let has_conflict_target: HashSet<String> = generated
2331 .object_types
2332 .iter()
2333 .filter(|(_, object)| !object.table.unique_constraints.is_empty())
2334 .map(|(_, object)| object.local_name.clone())
2335 .collect();
2336
2337 for field in &generated.mutation_fields {
2338 let table_name = field.table_name.clone();
2339 let schema_name = field.schema_name.clone();
2340 let mutation_type = field.mutation_type;
2341 let pk_columns = field.pk_columns.clone();
2342 let return_type = graphql_type_ref(&field.return_type);
2343
2344 let where_type = field.local_type_name.clone();
2348
2349 let resolver_pk_columns = pk_columns.clone();
2350 let field_relationships = Arc::clone(&relationships);
2351 let field_type_names = Arc::clone(&type_names);
2352 let field_names = Arc::clone(&names);
2353 let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2354 let table_name = table_name.clone();
2355 let schema_name = schema_name.clone();
2356 let pk_columns = resolver_pk_columns.clone();
2357 let relationships = Arc::clone(&field_relationships);
2358 let type_names = Arc::clone(&field_type_names);
2359 let names = Arc::clone(&field_names);
2360 FieldFuture::new(async move {
2361 resolve_mutation(
2362 &ctx,
2363 &schema_name,
2364 &table_name,
2365 mutation_type,
2366 &pk_columns,
2367 &relationships,
2368 &type_names,
2369 &names,
2370 max_rows,
2371 )
2372 .await
2373 })
2374 });
2375
2376 match mutation_type {
2382 MutationType::Insert | MutationType::InsertOne => {
2383 let insert_input = format!("{}_insert_input", where_type);
2384 gql_field = if mutation_type == MutationType::Insert {
2385 gql_field.argument(InputValue::new(
2386 "objects",
2387 TypeRef::named_nn_list_nn(&insert_input),
2388 ))
2389 } else {
2390 gql_field.argument(InputValue::new("object", TypeRef::named_nn(&insert_input)))
2393 };
2394 if has_conflict_target.contains(&where_type) {
2395 gql_field = gql_field.argument(InputValue::new(
2396 "on_conflict",
2397 TypeRef::named(format!("{}_on_conflict", where_type)),
2398 ));
2399 }
2400 }
2401 MutationType::UpdateByPk => {
2402 gql_field = gql_field.argument(InputValue::new(
2403 "pk_columns",
2404 TypeRef::named_nn(format!("{}_pk_columns_input", where_type)),
2405 ));
2406 gql_field = with_update_operators(
2407 gql_field,
2408 &where_type,
2409 has_numeric_column.contains(&where_type),
2410 has_jsonb_column.contains(&where_type),
2411 );
2412 }
2413 MutationType::Update => {
2414 gql_field = gql_field.argument(InputValue::new(
2415 "where",
2416 TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2417 ));
2418 gql_field = with_update_operators(
2419 gql_field,
2420 &where_type,
2421 has_numeric_column.contains(&where_type),
2422 has_jsonb_column.contains(&where_type),
2423 );
2424 }
2425 MutationType::UpdateMany => {
2426 gql_field = gql_field.argument(InputValue::new(
2427 "updates",
2428 TypeRef::named_nn_list_nn(format!("{}_updates", where_type)),
2429 ));
2430 }
2431 MutationType::DeleteByPk => {
2432 for (col_name, pg_type) in &pk_columns {
2433 gql_field = gql_field.argument(InputValue::new(
2434 names
2435 .column(&field.schema_name, &field.table_name, col_name)
2436 .unwrap_or(col_name),
2437 TypeRef::named_nn(exposed_pk_argument_type(
2438 names.as_ref(),
2439 &field.schema_name,
2440 &field.table_name,
2441 col_name,
2442 pg_type,
2443 )),
2444 ));
2445 }
2446 }
2447 MutationType::Delete => {
2448 gql_field = gql_field.argument(InputValue::new(
2455 "where",
2456 TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2457 ));
2458 }
2459 }
2460
2461 if let Some(desc) = &field.description {
2462 gql_field = gql_field.description(desc);
2463 }
2464
2465 roots.push((field.name.clone(), gql_field));
2466 }
2467
2468 roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2469 for (_, field) in roots {
2470 mutation = mutation.field(field);
2471 }
2472
2473 mutation
2474}
2475
2476fn create_subscription_type(
2493 generated: &GeneratedSchema,
2494 root_name: &str,
2495 max_rows: Option<i64>,
2496 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2497 names: Arc<crate::names::NameOverrides>,
2498 refresh: std::time::Duration,
2499) -> Subscription {
2500 let mut roots: Vec<(String, SubscriptionField)> = Vec::new();
2501
2502 for field in &generated.query_fields {
2503 let spec_type_name = field.local_type_name.clone();
2504 let pk_columns = field.pk_columns.clone();
2505 let is_by_pk = field.is_by_pk;
2506 let return_type = graphql_type_ref(&field.return_type);
2507 let watched = vec![crate::subscription::table_channel_name(
2508 &field.schema_name,
2509 &field.table_name,
2510 )];
2511
2512 let spec = Arc::new(QueryFieldSpec {
2513 schema_name: field.schema_name.clone(),
2514 table_name: field.table_name.clone(),
2515 type_name: field.type_name.clone(),
2516 is_by_pk,
2517 pk_columns: pk_columns.clone(),
2518 max_rows,
2519 relationships: Arc::clone(&relationships),
2520 names: Arc::clone(&names),
2521 call: None,
2522 });
2523
2524 let mut gql_field = SubscriptionField::new(&field.name, return_type, move |ctx| {
2525 let spec = Arc::clone(&spec);
2526 let watched = watched.clone();
2527 SubscriptionFieldFuture::new(async move {
2528 let wake = wake_on(&ctx, &watched, refresh).await;
2529 Ok(live(ctx, LiveSource::Rows(spec), wake))
2530 })
2531 });
2532
2533 gql_field = match is_by_pk {
2534 false => with_row_arguments(gql_field, &spec_type_name),
2535 true => with_key_arguments(
2536 gql_field,
2537 &field.schema_name,
2538 &field.table_name,
2539 &pk_columns,
2540 names.as_ref(),
2541 ),
2542 };
2543 if let Some(desc) = &field.description {
2544 gql_field = gql_field.description(desc);
2545 }
2546 if field.rows {
2548 roots.push((field.name.clone(), gql_field));
2549 }
2550
2551 if is_by_pk || !field.aggregates {
2554 continue;
2555 }
2556
2557 let agg_spec = Arc::new(AggregateSpec {
2559 schema_name: field.schema_name.clone(),
2560 table_name: field.table_name.clone(),
2561 type_name: field.type_name.clone(),
2562 max_rows,
2563 relationships: Arc::clone(&relationships),
2564 names: Arc::clone(&names),
2565 call: None,
2566 });
2567 let agg_watched = vec![crate::subscription::table_channel_name(
2568 &field.schema_name,
2569 &field.table_name,
2570 )];
2571 let agg_name = field.aggregate_name.clone().unwrap_or_else(|| {
2572 crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2573 });
2574 let mut agg_field = SubscriptionField::new(
2575 agg_name.clone(),
2576 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2577 &field.local_type_name,
2578 )),
2579 move |ctx| {
2580 let agg_spec = Arc::clone(&agg_spec);
2581 let watched = agg_watched.clone();
2582 SubscriptionFieldFuture::new(async move {
2583 let wake = wake_on(&ctx, &watched, refresh).await;
2584 Ok(live(ctx, LiveSource::Aggregate(agg_spec), wake))
2585 })
2586 },
2587 );
2588 agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2589 agg_field = match field.aggregate_description.as_deref() {
2590 Some("") => agg_field,
2591 Some(given) => agg_field.description(given),
2592 None => agg_field.description(format!(
2593 "fetch aggregated fields from the table: \"{}\"",
2594 field.table_name
2595 )),
2596 };
2597 roots.push((agg_name, agg_field));
2598 }
2599
2600 roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2601 let mut subscription = Subscription::new(root_name);
2602 if roots.is_empty() {
2605 return subscription.field(SubscriptionField::new(
2606 "no_subscriptions_available",
2607 TypeRef::named("String"),
2608 |_| {
2609 SubscriptionFieldFuture::new(async move {
2610 Ok(futures::stream::empty::<
2611 Result<FieldValue, async_graphql::Error>,
2612 >())
2613 })
2614 },
2615 ));
2616 }
2617 for (_, field) in roots {
2618 subscription = subscription.field(field);
2619 }
2620 subscription
2621}
2622
2623async fn wake_on(
2629 ctx: &ResolverContext<'_>,
2630 channels: &[String],
2631 refresh: std::time::Duration,
2632) -> futures::stream::BoxStream<'static, ()> {
2633 use futures::StreamExt;
2634
2635 let mut streams: Vec<futures::stream::BoxStream<'static, ()>> =
2636 Vec::with_capacity(channels.len() + 1);
2637 streams.push(
2640 tokio_stream::wrappers::IntervalStream::new({
2641 let mut interval = tokio::time::interval(refresh);
2642 interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
2643 interval
2644 })
2645 .map(|_| ())
2646 .boxed(),
2647 );
2648
2649 if let Ok(broker_arc) = ctx.data::<Arc<RwLock<Option<NotifyBroker>>>>() {
2650 let guard = broker_arc.read().await;
2651 if let Some(broker) = guard.as_ref() {
2652 for channel in channels {
2653 streams.push(
2657 broker
2658 .subscribe_or_create(channel)
2659 .await
2660 .map(|_| ())
2661 .boxed(),
2662 );
2663 }
2664 }
2665 }
2666
2667 futures::stream::select_all(streams).boxed()
2668}
2669
2670enum LiveSource {
2676 Rows(Arc<QueryFieldSpec>),
2677 Aggregate(Arc<AggregateSpec>),
2678}
2679
2680async fn read_live<'a>(
2682 ctx: &ResolverContext<'a>,
2683 source: &LiveSource,
2684) -> Result<(serde_json::Value, Option<FieldValue<'a>>), async_graphql::Error> {
2685 match source {
2686 LiveSource::Rows(spec) => {
2687 let rows = query_rows(ctx, spec).await?;
2688 Ok((
2689 serde_json::Value::Array(rows.clone()),
2690 rows_as_field_value(rows, spec.is_by_pk),
2691 ))
2692 }
2693 LiveSource::Aggregate(spec) => {
2694 let value = aggregate_value(ctx, spec).await?;
2695 let seen = serde_json::to_value(&value).unwrap_or_default();
2696 Ok((seen, Some(FieldValue::value(value))))
2697 }
2698 }
2699}
2700
2701fn live<'a>(
2709 ctx: ResolverContext<'a>,
2710 source: LiveSource,
2711 wake: futures::stream::BoxStream<'static, ()>,
2712) -> impl futures::Stream<Item = Result<FieldValue<'a>, async_graphql::Error>> + Send + 'a {
2713 use futures::StreamExt;
2714
2715 struct Live<'a> {
2716 ctx: ResolverContext<'a>,
2717 source: LiveSource,
2718 wake: futures::stream::BoxStream<'static, ()>,
2719 sent: Option<serde_json::Value>,
2720 started: bool,
2721 }
2722
2723 futures::stream::unfold(
2724 Live {
2725 ctx,
2726 source,
2727 wake,
2728 sent: None,
2729 started: false,
2730 },
2731 |mut live: Live<'a>| async move {
2732 loop {
2733 if live.started {
2734 live.wake.next().await?;
2735 } else {
2736 live.started = true;
2737 }
2738 let read = read_live(&live.ctx, &live.source).await;
2739 match read {
2740 Err(e) => return Some((Err(e), live)),
2744 Ok((seen, value)) => {
2745 if live.sent.as_ref() == Some(&seen) {
2746 continue;
2747 }
2748 live.sent = Some(seen);
2749 let value = value.unwrap_or_else(|| FieldValue::value(Value::Null));
2750 return Some((Ok(value), live));
2751 }
2752 }
2753 }
2754 },
2755 )
2756 .take_while(|item| futures::future::ready(item.is_ok()))
2757}
2758
2759struct QueryFieldSpec {
2761 schema_name: String,
2762 table_name: String,
2763 type_name: String,
2764 is_by_pk: bool,
2765 pk_columns: Vec<(String, String)>,
2766 max_rows: Option<i64>,
2767 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2768 names: Arc<crate::names::NameOverrides>,
2769 call: Option<Arc<FunctionCall>>,
2772}
2773
2774#[derive(Clone)]
2776struct EntityLookup {
2777 schema_name: String,
2778 table_name: String,
2779 type_name: String,
2780 key_columns: Vec<EntityKeyColumn>,
2781 row_column_types: HashMap<String, String>,
2782}
2783
2784#[derive(Clone)]
2785struct EntityKeyColumn {
2786 field_name: String,
2787 column_name: String,
2788 pg_type: String,
2789 explicit_id: bool,
2790}
2791
2792struct EntityResolverState {
2794 entities: HashMap<String, EntityLookup>,
2795 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2796 names: Arc<crate::names::NameOverrides>,
2797 max_rows: Option<i64>,
2798}
2799
2800impl EntityResolverState {
2801 fn new(
2802 generated: &GeneratedSchema,
2803 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2804 names: Arc<crate::names::NameOverrides>,
2805 max_rows: Option<i64>,
2806 ) -> Self {
2807 let mut entities = HashMap::new();
2808 for (type_name, entity) in &generated.federation_entities {
2809 let Some(object) = generated.object_types.get(type_name) else {
2810 continue;
2811 };
2812 let mut key_columns = Vec::with_capacity(entity.key_fields.len());
2813 for field_name in &entity.key_fields {
2814 let column_name = names
2815 .column_source(&object.table.schema, &object.table.name, field_name)
2816 .unwrap_or(field_name);
2817 let Some(column) = object.table.get_column(column_name) else {
2818 continue;
2819 };
2820 key_columns.push(EntityKeyColumn {
2821 field_name: field_name.clone(),
2822 column_name: column.name.clone(),
2823 pg_type: column.nominal_type.clone(),
2824 explicit_id: matches!(
2825 names.column_type(&object.table.schema, &object.table.name, &column.name),
2826 Some(crate::types::GraphQLType::Id)
2827 ),
2828 });
2829 }
2830 if key_columns.len() != entity.key_fields.len() {
2831 continue;
2832 }
2833 entities.insert(
2834 type_name.clone(),
2835 EntityLookup {
2836 schema_name: object.table.schema.clone(),
2837 table_name: object.table.name.clone(),
2838 type_name: type_name.clone(),
2839 key_columns,
2840 row_column_types: exposed_column_types(&object.table, names.as_ref()),
2841 },
2842 );
2843 }
2844 Self {
2845 entities,
2846 relationships,
2847 names,
2848 max_rows,
2849 }
2850 }
2851}
2852
2853struct EntityRepresentation {
2854 index: usize,
2855 type_name: String,
2856 values: serde_json::Map<String, serde_json::Value>,
2857}
2858
2859fn entity_field_applicability(
2866 selection_set: &SelectionSet,
2867 fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2868 type_name: &str,
2869) -> Vec<bool> {
2870 fn condition_matches(condition: Option<&Positioned<TypeCondition>>, type_name: &str) -> bool {
2871 condition.is_none_or(|condition| {
2872 let condition = condition.node.on.node.as_str();
2873 condition == type_name || condition == "_Entity"
2874 })
2875 }
2876
2877 fn collect(
2878 selection_set: &SelectionSet,
2879 fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2880 type_name: &str,
2881 parent_matches: bool,
2882 applicability: &mut Vec<bool>,
2883 ) {
2884 for selection in &selection_set.items {
2885 match &selection.node {
2886 Selection::Field(_) => applicability.push(parent_matches),
2887 Selection::FragmentSpread(spread) => {
2888 let Some(fragment) = fragments.get(&spread.node.fragment_name.node) else {
2889 continue;
2890 };
2891 collect(
2892 &fragment.node.selection_set.node,
2893 fragments,
2894 type_name,
2895 parent_matches
2896 && condition_matches(Some(&fragment.node.type_condition), type_name),
2897 applicability,
2898 );
2899 }
2900 Selection::InlineFragment(fragment) => collect(
2901 &fragment.node.selection_set.node,
2902 fragments,
2903 type_name,
2904 parent_matches
2905 && condition_matches(fragment.node.type_condition.as_ref(), type_name),
2906 applicability,
2907 ),
2908 }
2909 }
2910 }
2911
2912 let mut applicability = Vec::new();
2913 collect(
2914 selection_set,
2915 fragments,
2916 type_name,
2917 true,
2918 &mut applicability,
2919 );
2920 applicability
2921}
2922
2923fn entity_selection_fields<'ctx, 'a>(
2924 ctx: &'ctx ResolverContext<'a>,
2925 type_name: &str,
2926) -> Vec<SelectionField<'ctx>> {
2927 let applicability = entity_field_applicability(
2928 &ctx.ctx.item.node.selection_set.node,
2929 &ctx.ctx.query_env.fragments,
2930 type_name,
2931 );
2932 let fields: Vec<_> = ctx.field().selection_set().collect();
2933 debug_assert_eq!(fields.len(), applicability.len());
2934 fields
2935 .into_iter()
2936 .zip(applicability)
2937 .filter_map(|(field, applies)| applies.then_some(field))
2938 .collect()
2939}
2940
2941async fn resolve_entities<'a>(
2942 ctx: &ResolverContext<'a>,
2943 state: &EntityResolverState,
2944) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
2945 let representations = ctx.args.try_get("representations")?.list()?;
2946 let mut grouped: HashMap<String, Vec<EntityRepresentation>> = HashMap::new();
2947 let mut count = 0;
2948 for (index, representation) in representations.iter().enumerate() {
2949 count += 1;
2950 let value = accessor_to_json(&representation);
2951 let serde_json::Value::Object(values) = value else {
2952 return Err(async_graphql::Error::new(
2953 "entity representation must be an object",
2954 ));
2955 };
2956 let type_name = values
2957 .get("__typename")
2958 .and_then(serde_json::Value::as_str)
2959 .ok_or_else(|| {
2960 async_graphql::Error::new("entity representation is missing __typename")
2961 })?
2962 .to_string();
2963 grouped
2964 .entry(type_name.clone())
2965 .or_default()
2966 .push(EntityRepresentation {
2967 index,
2968 type_name,
2969 values,
2970 });
2971 }
2972
2973 let mut resolved: Vec<Option<FieldValue<'a>>> =
2974 std::iter::repeat_with(|| None).take(count).collect();
2975 for (type_name, representations) in grouped {
2976 let lookup = state.entities.get(&type_name).ok_or_else(|| {
2977 async_graphql::Error::new(format!("unknown federation entity \"{}\"", type_name))
2978 })?;
2979 let rows = resolve_entity_group(ctx, lookup, &representations, state).await?;
2980 for (ordinality, representation) in representations.into_iter().enumerate() {
2981 resolved[representation.index] = Some(match &rows[ordinality] {
2982 Some(row) => FieldValue::value(json_to_value(row.clone()))
2983 .with_type(representation.type_name),
2984 None => FieldValue::NULL,
2985 });
2986 }
2987 }
2988
2989 Ok(Some(FieldValue::list(resolved.into_iter().map(|value| {
2990 value.expect("every representation was resolved or errored")
2991 }))))
2992}
2993
2994async fn resolve_entity_group(
2995 ctx: &ResolverContext<'_>,
2996 lookup: &EntityLookup,
2997 representations: &[EntityRepresentation],
2998 state: &EntityResolverState,
2999) -> Result<Vec<Option<serde_json::Value>>, async_graphql::Error> {
3000 if representations.is_empty() {
3001 return Ok(Vec::new());
3002 }
3003
3004 let pool = ctx.data::<PgPool>()?;
3005 let gql_ctx = ctx.data::<GraphQLContext>()?;
3006 let mut bound_values = Vec::new();
3007 let mut value_rows = Vec::with_capacity(representations.len());
3008 for (ordinality, representation) in representations.iter().enumerate() {
3009 let normalized_values = normalized_entity_key_values(
3010 &representation.values,
3011 &lookup.key_columns,
3012 &lookup.type_name,
3013 )?;
3014 let mut values = Vec::with_capacity(lookup.key_columns.len() + 1);
3015 for (key_column, value) in lookup.key_columns.iter().zip(normalized_values) {
3016 values.push(format!(
3017 "${}::{}",
3018 bound_values.len() + 1,
3019 key_column.pg_type
3020 ));
3021 bound_values.push(value);
3022 }
3023 values.push(ordinality.to_string());
3024 value_rows.push(format!("({})", values.join(", ")));
3025 }
3026
3027 let representation_alias = "pgrst_entity_representation";
3028 let ordinality_column = "pgrst_entity_ordinality";
3029 let key_aliases: Vec<String> = (0..lookup.key_columns.len())
3030 .map(|index| format!("pgrst_entity_key_{index}"))
3031 .collect();
3032 let mut representation_columns: Vec<String> = key_aliases
3033 .iter()
3034 .map(|column| postrust_sql::escape_ident(column))
3035 .collect();
3036 representation_columns.push(postrust_sql::escape_ident(ordinality_column));
3037 let representations_sql = format!(
3038 "(VALUES {}) AS {} ({})",
3039 value_rows.join(", "),
3040 postrust_sql::escape_ident(representation_alias),
3041 representation_columns.join(", ")
3042 );
3043 let key_predicate = lookup
3044 .key_columns
3045 .iter()
3046 .zip(&key_aliases)
3047 .map(|(key_column, key_alias)| {
3048 format!(
3049 "{}.{} = {}.{}",
3050 postrust_sql::escape_ident(READ_ROW),
3051 postrust_sql::escape_ident(&key_column.column_name),
3052 postrust_sql::escape_ident(representation_alias),
3053 postrust_sql::escape_ident(key_alias)
3054 )
3055 })
3056 .collect::<Vec<_>>()
3057 .join(" AND ");
3058 let mut where_sql = format!(" WHERE ({key_predicate})");
3059
3060 let permission = permission_predicate(
3061 &gql_ctx.caller(),
3062 state.names.as_ref(),
3063 &lookup.schema_name,
3064 &lookup.table_name,
3065 )?;
3066 if let Some(predicate) = permission {
3067 let guard = gql_ctx
3068 .schema_cache
3069 .get()
3070 .await
3071 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3072 let cache = guard
3073 .as_ref()
3074 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3075 let scope = WhereScope::table(
3076 &lookup.schema_name,
3077 &lookup.table_name,
3078 &lookup.type_name,
3079 state.names.as_ref(),
3080 )
3081 .under_alias(READ_ROW)
3082 .with_resolution(cache, state.relationships.as_ref())
3083 .for_caller(gql_ctx.caller());
3084 let (filter_sql, filter_values) =
3085 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3086 if !filter_sql.is_empty() {
3087 where_sql = format!(
3088 "{} AND ({})",
3089 where_sql,
3090 filter_sql.trim_start_matches("WHERE ")
3091 );
3092 bound_values.extend(filter_values);
3093 }
3094 }
3095
3096 let source = format!(
3097 "{}.{} AS {}",
3098 postrust_sql::escape_ident(&lookup.schema_name),
3099 postrust_sql::escape_ident(&lookup.table_name),
3100 postrust_sql::escape_ident(READ_ROW)
3101 );
3102 let source_rows = format!(
3103 "SELECT {}.* FROM {}{}",
3104 postrust_sql::escape_ident(READ_ROW),
3105 source,
3106 where_sql
3107 );
3108 let mut projection = {
3109 let guard = gql_ctx
3110 .schema_cache
3111 .get()
3112 .await
3113 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3114 guard
3115 .as_ref()
3116 .and_then(|cache| {
3117 cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3118 &lookup.schema_name,
3119 &lookup.table_name,
3120 ))
3121 })
3122 .and_then(|table| rename_projection(table, "src", state.names.as_ref()))
3123 .unwrap_or_else(|| "src.*".to_string())
3124 };
3125 let projected_rows = {
3126 let guard = gql_ctx
3127 .schema_cache
3128 .get()
3129 .await
3130 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3131 let cache = guard
3132 .as_ref()
3133 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3134 let table = cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3135 &lookup.schema_name,
3136 &lookup.table_name,
3137 ));
3138 let selected_fields = entity_selection_fields(ctx, &lookup.type_name);
3139 let mut param_idx = bound_values.len() + 1;
3140 let computed = match table {
3141 Some(table) => computed_projections(
3142 table,
3143 selected_fields.iter().copied(),
3144 "src",
3145 state.names.as_ref(),
3146 cache,
3147 &mut param_idx,
3148 &mut bound_values,
3149 )?,
3150 None => Vec::new(),
3151 };
3152 let embeds = build_embed_expressions(
3153 &gql_ctx.caller(),
3154 cache,
3155 state.relationships.as_ref(),
3156 &lookup.type_name,
3157 "src",
3158 selected_fields,
3159 state.max_rows,
3160 &mut 0,
3161 &mut param_idx,
3162 &mut bound_values,
3163 state.names.as_ref(),
3164 )?;
3165 for expression in &computed {
3166 projection.push_str(", ");
3167 projection.push_str(expression);
3168 }
3169 for (field_name, expression) in &embeds {
3170 projection.push_str(", ");
3171 projection.push_str(expression);
3172 projection.push_str(" AS ");
3173 projection.push_str(&postrust_sql::escape_ident(field_name));
3174 }
3175 format!("SELECT {} FROM ({}) AS src", projection, source_rows)
3176 };
3177 let sql = format!(
3182 "SELECT json_build_array({}.{}, {}) FROM {} CROSS JOIN LATERAL ({}) AS t",
3183 postrust_sql::escape_ident(representation_alias),
3184 postrust_sql::escape_ident(ordinality_column),
3185 row_json("t", &lookup.row_column_types),
3186 representations_sql,
3187 projected_rows,
3188 );
3189 let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3190 let rows = execute_query_on(&mut tx, &sql, &bound_values).await?;
3191 tx.commit().await?;
3192
3193 let mut by_ordinal = std::iter::repeat_with(|| None)
3194 .take(representations.len())
3195 .collect::<Vec<_>>();
3196 for result in rows {
3197 let serde_json::Value::Array(mut pair) = result else {
3198 return Err(async_graphql::Error::new(
3199 "entity query returned an invalid result",
3200 ));
3201 };
3202 if pair.len() != 2 {
3203 return Err(async_graphql::Error::new(
3204 "entity query returned an invalid result",
3205 ));
3206 }
3207 let row = pair.pop().expect("the result pair has two values");
3208 let ordinality = pair
3209 .pop()
3210 .and_then(|value| value.as_u64())
3211 .and_then(|value| usize::try_from(value).ok())
3212 .filter(|value| *value < by_ordinal.len())
3213 .ok_or_else(|| async_graphql::Error::new("entity query returned an invalid ordinal"))?;
3214 by_ordinal[ordinality] = Some(row);
3215 }
3216 Ok(by_ordinal)
3217}
3218
3219fn normalized_entity_key_values(
3223 values: &serde_json::Map<String, serde_json::Value>,
3224 key_columns: &[EntityKeyColumn],
3225 type_name: &str,
3226) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
3227 let mut key = Vec::with_capacity(key_columns.len());
3228 for key_column in key_columns {
3229 let value = values.get(&key_column.field_name).ok_or_else(|| {
3230 async_graphql::Error::new(format!(
3231 "entity representation for \"{}\" is missing key field \"{}\"",
3232 type_name, key_column.field_name
3233 ))
3234 })?;
3235 key.push(normalize_entity_key_value(type_name, key_column, value)?);
3236 }
3237 Ok(key)
3238}
3239
3240fn normalize_entity_key_value(
3241 type_name: &str,
3242 key_column: &EntityKeyColumn,
3243 value: &serde_json::Value,
3244) -> Result<serde_json::Value, async_graphql::Error> {
3245 let invalid = |expected: &str| {
3246 async_graphql::Error::new(format!(
3247 "entity representation for \"{}\" has invalid key field \"{}\": expected {}, found {}",
3248 type_name,
3249 key_column.field_name,
3250 expected,
3251 json_value_kind(value)
3252 ))
3253 };
3254
3255 if key_column.explicit_id {
3256 return normalize_graphql_id(value, &key_column.pg_type).ok_or_else(|| {
3257 invalid(&format!(
3258 "an ID compatible with PostgreSQL type \"{}\"",
3259 key_column.pg_type
3260 ))
3261 });
3262 }
3263
3264 let pg_type = key_column.pg_type.trim().to_ascii_lowercase();
3265 let normalized = if matches!(pg_type.as_str(), "int8" | "bigint") {
3266 match value {
3267 serde_json::Value::Number(number) => number.as_i64(),
3268 serde_json::Value::String(text) => text.parse::<i64>().ok(),
3269 _ => None,
3270 }
3271 .map(serde_json::Value::from)
3272 } else if integer_bounds(&pg_type).is_some() {
3273 value.as_i64().and_then(|integer| {
3274 let (min, max) = integer_bounds(&pg_type)?;
3275 (min..=max)
3276 .contains(&integer)
3277 .then(|| serde_json::Value::from(integer))
3278 })
3279 } else if matches!(
3280 pg_type.as_str(),
3281 "float4" | "float8" | "real" | "double precision"
3282 ) {
3283 value
3284 .as_f64()
3285 .and_then(serde_json::Number::from_f64)
3286 .map(serde_json::Value::Number)
3287 } else if matches!(pg_type.as_str(), "numeric" | "decimal") {
3288 match value {
3289 serde_json::Value::Number(number) => {
3290 Some(serde_json::Value::String(number.to_string()))
3291 }
3292 serde_json::Value::String(text) if text.parse::<sqlx::types::BigDecimal>().is_ok() => {
3293 Some(serde_json::Value::String(text.clone()))
3294 }
3295 _ => None,
3296 }
3297 } else if matches!(pg_type.as_str(), "bool" | "boolean") {
3298 value.as_bool().map(serde_json::Value::Bool)
3299 } else if matches!(pg_type.as_str(), "json" | "jsonb") {
3300 Some(value.clone())
3301 } else if is_array_type(&pg_type) {
3302 value.is_array().then(|| value.clone())
3303 } else {
3304 value
3305 .as_str()
3306 .map(|text| serde_json::Value::String(text.to_string()))
3307 };
3308
3309 normalized.ok_or_else(|| {
3310 invalid(&format!(
3311 "a value compatible with PostgreSQL type \"{}\"",
3312 key_column.pg_type
3313 ))
3314 })
3315}
3316
3317fn normalize_graphql_id(value: &serde_json::Value, pg_type: &str) -> Option<serde_json::Value> {
3318 if integer_bounds(pg_type).is_some() {
3319 let integer = match value {
3320 serde_json::Value::Number(number) => number.as_i64(),
3321 serde_json::Value::String(text) => text.parse::<i64>().ok(),
3322 _ => None,
3323 }?;
3324 let (min, max) = integer_bounds(pg_type)?;
3325 return (min..=max)
3326 .contains(&integer)
3327 .then(|| serde_json::Value::from(integer));
3328 }
3329 if pg_type.eq_ignore_ascii_case("uuid") {
3330 return normalize_uuid(value).map(serde_json::Value::String);
3331 }
3332 if is_text_type(pg_type) {
3333 return match value {
3334 serde_json::Value::String(s) => Some(serde_json::Value::String(s.clone())),
3335 serde_json::Value::Number(n) if n.is_i64() || n.is_u64() => {
3336 Some(serde_json::Value::String(n.to_string()))
3337 }
3338 _ => None,
3339 };
3340 }
3341 None
3342}
3343
3344fn integer_bounds(pg_type: &str) -> Option<(i64, i64)> {
3345 match pg_type.trim().to_ascii_lowercase().as_str() {
3346 "int2" | "smallint" => Some((i16::MIN.into(), i16::MAX.into())),
3347 "int4" | "int" | "integer" => Some((i32::MIN.into(), i32::MAX.into())),
3348 "int8" | "bigint" => Some((i64::MIN, i64::MAX)),
3349 _ => None,
3350 }
3351}
3352
3353fn is_text_type(pg_type: &str) -> bool {
3354 matches!(
3355 pg_type.trim().to_ascii_lowercase().as_str(),
3356 "text" | "varchar" | "character varying" | "char" | "character" | "bpchar" | "citext"
3357 )
3358}
3359
3360fn normalize_uuid(value: &serde_json::Value) -> Option<String> {
3361 sqlx::types::Uuid::parse_str(value.as_str()?)
3362 .ok()
3363 .map(|uuid| uuid.to_string())
3364}
3365
3366fn json_value_kind(value: &serde_json::Value) -> &'static str {
3367 match value {
3368 serde_json::Value::Null => "null",
3369 serde_json::Value::Bool(_) => "a boolean",
3370 serde_json::Value::Number(_) => "a number",
3371 serde_json::Value::String(_) => "a string",
3372 serde_json::Value::Array(_) => "an array",
3373 serde_json::Value::Object(_) => "an object",
3374 }
3375}
3376
3377fn add_function_fields(
3384 mut root: Object,
3385 generated: &GeneratedSchema,
3386 volatile: bool,
3387 max_rows: Option<i64>,
3388 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3389 names: Arc<crate::names::NameOverrides>,
3390) -> Object {
3391 let taken: HashSet<&str> = if volatile {
3396 generated
3397 .mutation_fields
3398 .iter()
3399 .map(|f| f.name.as_str())
3400 .collect()
3401 } else {
3402 generated
3403 .query_fields
3404 .iter()
3405 .map(|f| f.name.as_str())
3406 .collect()
3407 };
3408
3409 for function in &generated.function_fields {
3410 if function.volatile != volatile {
3411 continue;
3412 }
3413 if taken.contains(function.name.as_str()) {
3414 tracing::warn!(
3415 "not exposing {}.{}({}) -- a table already has a root field \
3416 called {}",
3417 function.schema_name,
3418 function.function_name,
3419 function.argument_signature(),
3420 function.name,
3421 );
3422 continue;
3423 }
3424 let spec = Arc::new(QueryFieldSpec {
3425 schema_name: function.returns_table.0.clone(),
3426 table_name: function.returns_table.1.clone(),
3427 type_name: function.returns.clone(),
3428 is_by_pk: false,
3429 pk_columns: Vec::new(),
3430 max_rows,
3431 relationships: Arc::clone(&relationships),
3432 names: Arc::clone(&names),
3433 call: Some(Arc::new(FunctionCall {
3434 schema: function.schema_name.clone(),
3435 name: function.function_name.clone(),
3436 arguments: function.arguments.clone(),
3437 session_argument: function.session_argument.clone(),
3438 })),
3439 });
3440
3441 let mut field = Field::new(
3442 &function.name,
3443 TypeRef::named_nn_list_nn(&function.returns),
3444 move |ctx| {
3445 let spec = Arc::clone(&spec);
3446 FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
3447 },
3448 );
3449 if !function.arguments.is_empty() {
3450 field = field.argument(InputValue::new(
3451 "args",
3452 TypeRef::named_nn(format!("{}_args", function.name)),
3453 ));
3454 }
3455 field = field
3456 .argument(InputValue::new(
3457 "where",
3458 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
3459 &function.local_returns,
3460 )),
3461 ))
3462 .argument(InputValue::new(
3463 "order_by",
3464 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
3465 &function.local_returns,
3466 )),
3467 ))
3468 .argument(InputValue::new(
3469 "distinct_on",
3470 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
3471 &function.local_returns,
3472 )),
3473 ))
3474 .argument(InputValue::new("limit", TypeRef::named("Int")))
3475 .argument(InputValue::new("offset", TypeRef::named("Int")));
3476 if let Some(description) = &function.description {
3477 field = field.description(description);
3478 }
3479 root = root.field(field);
3480
3481 let agg_spec = Arc::new(AggregateSpec {
3486 schema_name: function.returns_table.0.clone(),
3487 table_name: function.returns_table.1.clone(),
3488 type_name: function.returns.clone(),
3489 max_rows,
3490 relationships: Arc::clone(&relationships),
3491 names: Arc::clone(&names),
3492 call: Some(Arc::new(FunctionCall {
3493 schema: function.schema_name.clone(),
3494 name: function.function_name.clone(),
3495 arguments: function.arguments.clone(),
3496 session_argument: function.session_argument.clone(),
3497 })),
3498 });
3499 let mut aggregate = Field::new(
3500 format!("{}_aggregate", function.name),
3501 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
3502 &function.local_returns,
3503 )),
3504 move |ctx| {
3505 let agg_spec = Arc::clone(&agg_spec);
3506 FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
3507 },
3508 );
3509 if !function.arguments.is_empty() {
3510 aggregate = aggregate.argument(InputValue::new(
3511 "args",
3512 TypeRef::named_nn(format!("{}_args", function.name)),
3513 ));
3514 }
3515 aggregate = with_row_arguments(aggregate, &function.local_returns).description(format!(
3516 "fetch aggregated fields from the table: \"{}\"",
3517 function.returns_table.1
3518 ));
3519 root = root.field(aggregate);
3520 }
3521 root
3522}
3523
3524fn function_source(
3532 ctx: &ResolverContext<'_>,
3533 call: &FunctionCall,
3534 alias: &str,
3535 bound_values: &mut Vec<serde_json::Value>,
3536) -> Result<String, async_graphql::Error> {
3537 let given = ctx
3538 .args
3539 .try_get("args")
3540 .ok()
3541 .map(|v| accessor_to_json(&v))
3542 .unwrap_or_else(|| serde_json::json!({}));
3543 let mut passed = Vec::new();
3544 for (name, _, required) in &call.arguments {
3545 match given.get(name) {
3546 None if *required => {
3547 return Err(async_graphql::Error::new(format!(
3548 "{} needs the argument \"{}\"",
3549 call.name, name
3550 )))
3551 }
3552 None => continue,
3555 Some(value) => {
3556 let (_, pg_type, _) = call
3563 .arguments
3564 .iter()
3565 .find(|(argument, _, _)| argument == name)
3566 .expect("iterating the arguments");
3567 passed.push(format!(
3568 "{} => ${}::{}",
3569 postrust_sql::escape_ident(name),
3570 bound_values.len() + 1,
3571 pg_type
3572 ));
3573 bound_values.push(value.clone());
3574 }
3575 }
3576 }
3577 if let Some(session) = &call.session_argument {
3581 passed.push(format!(
3582 "{} => {}",
3583 postrust_sql::escape_ident(session),
3584 SESSION_ARGUMENT
3585 ));
3586 }
3587 Ok(format!(
3588 "{}.{}({}) AS {}",
3589 postrust_sql::escape_ident(&call.schema),
3590 postrust_sql::escape_ident(&call.name),
3591 passed.join(", "),
3592 postrust_sql::escape_ident(alias)
3593 ))
3594}
3595
3596struct FunctionCall {
3598 schema: String,
3599 name: String,
3600 arguments: Vec<(String, String, bool)>,
3601 session_argument: Option<String>,
3604}
3605
3606struct AggregateSpec {
3608 schema_name: String,
3609 table_name: String,
3610 type_name: String,
3611 max_rows: Option<i64>,
3612 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3613 names: Arc<crate::names::NameOverrides>,
3614 call: Option<Arc<FunctionCall>>,
3618}
3619
3620async fn resolve_aggregate<'a>(
3627 ctx: &ResolverContext<'a>,
3628 spec: &AggregateSpec,
3629) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3630 Ok(Some(FieldValue::value(aggregate_value(ctx, spec).await?)))
3631}
3632
3633async fn aggregate_value(
3638 ctx: &ResolverContext<'_>,
3639 spec: &AggregateSpec,
3640) -> Result<Value, async_graphql::Error> {
3641 use crate::schema::aggregate as agg;
3642
3643 let pool = ctx.data::<PgPool>()?;
3644 let gql_ctx = ctx.data::<GraphQLContext>()?;
3645
3646 let mut bound_values: Vec<serde_json::Value> = Vec::new();
3647 let source = match &spec.call {
3651 None => format!(
3652 "{}.{}",
3653 postrust_sql::escape_ident(&spec.schema_name),
3654 postrust_sql::escape_ident(&spec.table_name)
3655 ),
3656 Some(call) => function_source(ctx, call, &spec.table_name, &mut bound_values)?,
3657 };
3658
3659 let mut where_sql = String::new();
3660 {
3661 let requested = ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v));
3665 let permission = permission_predicate(
3666 &gql_ctx.caller(),
3667 spec.names.as_ref(),
3668 &spec.schema_name,
3669 &spec.table_name,
3670 )?;
3671 if let Some(predicate) = and_predicates(requested, permission) {
3672 let guard = gql_ctx
3673 .schema_cache
3674 .get()
3675 .await
3676 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3677 let cache = guard
3678 .as_ref()
3679 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3680 let scope = match &spec.call {
3687 None => WhereScope::table(
3688 &spec.schema_name,
3689 &spec.table_name,
3690 &spec.type_name,
3691 spec.names.as_ref(),
3692 )
3693 .with_resolution(cache, spec.relationships.as_ref()),
3694 Some(_) => WhereScope::for_alias(
3695 &spec.schema_name,
3696 &spec.table_name,
3697 &spec.table_name,
3698 &spec.type_name,
3699 cache,
3700 spec.relationships.as_ref(),
3701 spec.names.as_ref(),
3702 ),
3703 }
3704 .for_caller(gql_ctx.caller());
3705 let (sql, values) =
3706 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3707 if !sql.is_empty() {
3708 where_sql = format!(" {}", sql);
3709 bound_values.extend(values);
3710 }
3711 }
3712 }
3713
3714 let order_sql = build_order_by_clause(
3715 ctx,
3716 &gql_ctx.schema_cache,
3717 &spec.schema_name,
3718 &spec.table_name,
3719 &spec.type_name,
3720 spec.relationships.as_ref(),
3721 &format!(
3722 "{}.{}",
3723 postrust_sql::escape_ident(&spec.schema_name),
3724 postrust_sql::escape_ident(&spec.table_name)
3725 ),
3726 spec.names.as_ref(),
3727 )
3728 .await?;
3729
3730 let requested_limit = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
3731 let offset = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
3732 let ceiling = match (
3733 spec.max_rows,
3734 permission_limit(
3735 &gql_ctx.caller(),
3736 spec.names.as_ref(),
3737 &spec.schema_name,
3738 &spec.table_name,
3739 ),
3740 ) {
3741 (Some(configured), Some(granted)) => Some(configured.min(granted)),
3742 (only, None) | (None, only) => only,
3743 };
3744 let limit = match (requested_limit, ceiling) {
3745 (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
3746 (Some(requested), None) => Some(requested),
3747 (None, ceiling) => ceiling,
3748 };
3749
3750 let rows = format!("SELECT * FROM {}{}{}", source, where_sql, order_sql);
3751
3752 let mut inner = rows.clone();
3754 if let Some(limit) = limit {
3755 inner.push_str(&format!(" LIMIT {}", limit));
3756 }
3757 if let Some(offset) = offset {
3758 inner.push_str(&format!(" OFFSET {}", offset));
3759 }
3760
3761 let mut counted = rows;
3775 if let Some(limit) = requested_limit {
3776 counted.push_str(&format!(" LIMIT {}", limit));
3777 }
3778 if let Some(offset) = offset {
3779 counted.push_str(&format!(" OFFSET {}", offset));
3780 }
3781
3782 let mut wants_nodes = false;
3787 let mut wanted: BTreeMap<String, Vec<String>> = BTreeMap::new();
3788 let mut counts: Vec<(String, String)> = Vec::new();
3791 for selection in ctx.field().selection_set() {
3792 match selection.name() {
3793 "nodes" => wants_nodes = true,
3794 "aggregate" => {
3795 for function in selection.selection_set() {
3796 if function.name() == "count" {
3797 counts.push((
3798 function.alias().unwrap_or("count").to_string(),
3799 count_expression(
3800 function,
3801 (&spec.schema_name, &spec.table_name),
3802 None,
3803 spec.names.as_ref(),
3804 ),
3805 ));
3806 continue;
3807 }
3808 let columns = wanted.entry(function.name().to_string()).or_default();
3809 for column in function.selection_set() {
3810 let name = column.name().to_string();
3811 if !columns.contains(&name) {
3812 columns.push(name);
3813 }
3814 }
3815 }
3816 }
3817 _ => {}
3818 }
3819 }
3820 wanted.retain(|_, columns| !columns.is_empty());
3821
3822 let mut result = async_graphql::indexmap::IndexMap::new();
3823
3824 if !counts.is_empty() || !wanted.is_empty() {
3825 let mut parts = match counts.iter().any(|(key, _)| key == "count") {
3829 true => Vec::new(),
3830 false => vec!["'count', count(*)".to_string()],
3831 };
3832 for (key, expression) in &counts {
3833 parts.push(format!("'{}', {}", key.replace('\'', "''"), expression));
3834 }
3835 for (function, columns) in &wanted {
3836 let sql_function = agg::NUMERIC_AGGREGATES
3840 .iter()
3841 .chain(agg::ORDERED_AGGREGATES.iter())
3842 .find(|(name, _)| name == function)
3843 .map(|(name, _)| *name)
3844 .ok_or_else(|| {
3845 async_graphql::Error::new(format!("unknown aggregate \"{}\"", function))
3846 })?;
3847 let per_column: Vec<String> = columns
3848 .iter()
3849 .map(|field| {
3850 let source = spec
3851 .names
3852 .column_source(&spec.schema_name, &spec.table_name, field)
3853 .unwrap_or(field);
3854 format!(
3855 "'{}', {}({})",
3856 field.replace('\'', "''"),
3857 sql_function,
3858 postrust_sql::escape_ident(source)
3859 )
3860 })
3861 .collect();
3862 parts.push(format!(
3863 "'{}', json_build_object({})",
3864 sql_function,
3865 per_column.join(", ")
3866 ));
3867 }
3868
3869 let sql = format!(
3870 "SELECT json_build_object({}) FROM ({}) AS pgrst_agg",
3871 parts.join(", "),
3872 counted
3873 );
3874 let mut conn =
3875 begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3876 let rows = execute_query_on(&mut conn, &sql, &bound_values).await?;
3877 conn.commit().await?;
3878 if let Some(first) = rows.into_iter().next() {
3879 if let Value::Object(map) = json_to_value(first) {
3880 result.insert(async_graphql::Name::new("aggregate"), Value::Object(map));
3881 }
3882 }
3883 }
3884
3885 if wants_nodes {
3886 let nodes = ctx
3892 .field()
3893 .selection_set()
3894 .find(|selection| selection.name() == "nodes")
3895 .ok_or_else(|| async_graphql::Error::new("nodes was asked for and then was not"))?;
3896
3897 let mut node_values = bound_values.clone();
3901 let (projection, row_column_types) = {
3902 let guard = gql_ctx
3903 .schema_cache
3904 .get()
3905 .await
3906 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3907 let cache = guard
3908 .as_ref()
3909 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3910 let qi = postrust_core::api_request::QualifiedIdentifier::new(
3911 &spec.schema_name,
3912 &spec.table_name,
3913 );
3914 let table = cache.get_table(&qi);
3915 let mut param_idx = node_values.len() + 1;
3916 let computed = match table {
3917 Some(table) => computed_projections(
3918 table,
3919 nodes.selection_set(),
3920 "src",
3921 spec.names.as_ref(),
3922 cache,
3923 &mut param_idx,
3924 &mut node_values,
3925 )?,
3926 None => Vec::new(),
3927 };
3928 let embeds = build_embed_expressions(
3929 &gql_ctx.caller(),
3930 cache,
3931 spec.relationships.as_ref(),
3932 &spec.type_name,
3933 "src",
3934 nodes.selection_set(),
3935 spec.max_rows,
3936 &mut 0,
3937 &mut param_idx,
3938 &mut node_values,
3939 spec.names.as_ref(),
3940 )?;
3941 let mut projection = table
3945 .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
3946 .unwrap_or_else(|| "src.*".to_string());
3947 for expression in &computed {
3948 projection.push_str(", ");
3949 projection.push_str(expression);
3950 }
3951 for (field_name, expression) in &embeds {
3952 projection.push_str(", ");
3953 projection.push_str(expression);
3954 projection.push_str(" AS ");
3955 projection.push_str(&postrust_sql::escape_ident(field_name));
3956 }
3957 (
3958 projection,
3959 table
3960 .map(|table| exposed_column_types(table, spec.names.as_ref()))
3961 .unwrap_or_default(),
3962 )
3963 };
3964
3965 let sql = format!(
3966 "SELECT {} FROM (SELECT {} FROM ({}) AS src) AS pgrst_nodes",
3967 row_json("pgrst_nodes", &row_column_types),
3968 projection,
3969 inner
3970 );
3971 let mut conn =
3972 begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3973 let rows = execute_query_on(&mut conn, &sql, &node_values).await?;
3974 conn.commit().await?;
3975 result.insert(
3976 async_graphql::Name::new("nodes"),
3977 Value::List(rows.into_iter().map(json_to_value).collect()),
3978 );
3979 }
3980
3981 let _ = &spec.type_name;
3982 Ok(Value::Object(result))
3983}
3984
3985async fn resolve_query<'a>(
3987 ctx: &ResolverContext<'a>,
3988 spec: &QueryFieldSpec,
3989) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3990 Ok(rows_as_field_value(
3991 query_rows(ctx, spec).await?,
3992 spec.is_by_pk,
3993 ))
3994}
3995
3996async fn query_rows(
4002 ctx: &ResolverContext<'_>,
4003 spec: &QueryFieldSpec,
4004) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4005 let schema_name = spec.schema_name.as_str();
4006 let table_name = spec.table_name.as_str();
4007 let type_name = spec.type_name.as_str();
4008 let is_by_pk = spec.is_by_pk;
4009 let pk_columns = spec.pk_columns.as_slice();
4010 let max_rows = spec.max_rows;
4011 let relationships = spec.relationships.as_ref();
4012
4013 let pool = ctx.data::<PgPool>()?;
4014 let gql_ctx = ctx.data::<GraphQLContext>()?;
4015
4016 debug!("Resolving query for table: {}", table_name);
4017
4018 let requested_limit: Option<i64> = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
4020
4021 let offset: Option<i64> = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
4022
4023 let limit: Option<i64> = if is_by_pk {
4028 Some(1)
4029 } else {
4030 let ceiling = match (
4034 max_rows,
4035 permission_limit(
4036 &gql_ctx.caller(),
4037 spec.names.as_ref(),
4038 schema_name,
4039 table_name,
4040 ),
4041 ) {
4042 (Some(configured), Some(granted)) => Some(configured.min(granted)),
4043 (only, None) | (None, only) => only,
4044 };
4045 match (requested_limit, ceiling) {
4046 (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
4047 (Some(requested), None) => Some(requested),
4048 (None, ceiling) => ceiling,
4049 }
4050 };
4051
4052 let mut where_sql = String::new();
4060 let mut bound_values: Vec<serde_json::Value> = Vec::new();
4061
4062 let source = match &spec.call {
4066 None => format!(
4072 "{}.{} AS {}",
4073 postrust_sql::escape_ident(schema_name),
4074 postrust_sql::escape_ident(table_name),
4075 postrust_sql::escape_ident(READ_ROW)
4076 ),
4077 Some(call) => function_source(ctx, call, table_name, &mut bound_values)?,
4078 };
4079
4080 let source_ref = match &spec.call {
4083 None => postrust_sql::escape_ident(READ_ROW),
4084 Some(_) => postrust_sql::escape_ident(table_name),
4085 };
4086
4087 if is_by_pk {
4088 if pk_columns.is_empty() {
4089 return Err(async_graphql::Error::new(format!(
4090 "\"{}\" has no primary key, so it cannot be queried by key",
4091 table_name
4092 )));
4093 }
4094
4095 let mut conditions = Vec::with_capacity(pk_columns.len());
4096 for (idx, (col_name, pg_type)) in pk_columns.iter().enumerate() {
4097 let field = spec
4098 .names
4099 .column(schema_name, table_name, col_name)
4100 .unwrap_or(col_name);
4101 let value = ctx.args.try_get(field).map_err(|_| {
4102 async_graphql::Error::new(format!(
4103 "missing required primary key argument \"{}\"",
4104 field
4105 ))
4106 })?;
4107
4108 conditions.push(format!(
4109 "{} = ${}::{}",
4110 postrust_sql::escape_ident(col_name),
4111 idx + 1,
4112 pg_type
4113 ));
4114 bound_values.push(accessor_to_json(&value));
4115 }
4116 where_sql = format!(" WHERE {}", conditions.join(" AND "));
4117 }
4118
4119 {
4124 let requested = match is_by_pk {
4125 true => None,
4126 false => ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v)),
4127 };
4128 let permission = permission_predicate(
4129 &gql_ctx.caller(),
4130 spec.names.as_ref(),
4131 schema_name,
4132 table_name,
4133 )?;
4134
4135 if let Some(predicate) = and_predicates(requested, permission) {
4136 let guard = gql_ctx
4137 .schema_cache
4138 .get()
4139 .await
4140 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4141 let cache = guard
4142 .as_ref()
4143 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4144 let scope = match &spec.call {
4145 None => WhereScope::table(schema_name, table_name, type_name, spec.names.as_ref())
4146 .under_alias(READ_ROW)
4147 .with_resolution(cache, relationships)
4148 .for_caller(gql_ctx.caller()),
4149 Some(_) => WhereScope::for_alias(
4152 schema_name,
4153 table_name,
4154 table_name,
4155 type_name,
4156 cache,
4157 relationships,
4158 spec.names.as_ref(),
4159 )
4160 .for_caller(gql_ctx.caller()),
4161 };
4162 let (filter_sql, filter_values) =
4163 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
4164 if !filter_sql.is_empty() {
4165 where_sql = match where_sql.is_empty() {
4168 true => format!(" {}", filter_sql),
4169 false => format!(
4170 "{} AND ({})",
4171 where_sql,
4172 filter_sql.trim_start_matches("WHERE ")
4173 ),
4174 };
4175 bound_values.extend(filter_values);
4176 }
4177 }
4178 }
4179
4180 let (order_sql, distinct_on) = if is_by_pk {
4183 (String::new(), Vec::new())
4184 } else {
4185 (
4186 build_order_by_clause(
4187 ctx,
4188 &gql_ctx.schema_cache,
4189 schema_name,
4190 table_name,
4191 type_name,
4192 relationships,
4193 &source_ref,
4194 spec.names.as_ref(),
4195 )
4196 .await?,
4197 build_distinct_on(
4198 ctx,
4199 &gql_ctx.schema_cache,
4200 schema_name,
4201 table_name,
4202 spec.names.as_ref(),
4203 )
4204 .await?,
4205 )
4206 };
4207
4208 let (distinct_sql, order_sql) = {
4216 let written = order_sql.strip_prefix(" ORDER BY ");
4217 let (prefix, order) = distinct_on_clause(&distinct_on, written).map_err(|error| {
4220 at_path(
4221 error,
4222 &format!(
4223 "$.selectionSet.{}.args",
4224 ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4225 ),
4226 )
4227 })?;
4228 (
4229 prefix,
4230 match order {
4231 Some(order) => format!(" ORDER BY {}", order),
4232 None => String::new(),
4233 },
4234 )
4235 };
4236
4237 let (computed, row_column_types) = {
4240 let guard = gql_ctx
4241 .schema_cache
4242 .get()
4243 .await
4244 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4245 match guard.as_ref() {
4246 Some(cache) => {
4247 let qi =
4248 postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
4249 match cache.get_table(&qi) {
4250 Some(table) => {
4251 let mut param_idx = bound_values.len() + 1;
4252 (
4253 computed_projections(
4254 table,
4255 ctx.field().selection_set(),
4256 "src",
4257 spec.names.as_ref(),
4258 cache,
4259 &mut param_idx,
4260 &mut bound_values,
4261 )?,
4262 exposed_column_types(table, spec.names.as_ref()),
4263 )
4264 }
4265 None => (Vec::new(), HashMap::new()),
4266 }
4267 }
4268 None => (Vec::new(), HashMap::new()),
4269 }
4270 };
4271 let mut inner = format!(
4275 "SELECT {}* FROM {}{}{}",
4276 distinct_sql, source, where_sql, order_sql
4277 );
4278
4279 if let Some(limit) = limit {
4280 inner.push_str(&format!(" LIMIT {}", limit));
4281 }
4282
4283 if let Some(offset) = offset {
4284 inner.push_str(&format!(" OFFSET {}", offset));
4285 }
4286
4287 let embed_expressions = {
4290 let guard = gql_ctx
4291 .schema_cache
4292 .get()
4293 .await
4294 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4295 match guard.as_ref() {
4296 Some(cache) => {
4297 let mut param_idx = bound_values.len() + 1;
4302 build_embed_expressions(
4303 &gql_ctx.caller(),
4304 cache,
4305 relationships,
4306 type_name,
4307 "src",
4308 ctx.field().selection_set(),
4309 max_rows,
4310 &mut 0,
4311 &mut param_idx,
4312 &mut bound_values,
4313 spec.names.as_ref(),
4314 )?
4315 }
4316 None => Vec::new(),
4317 }
4318 };
4319
4320 let renamed = {
4328 let guard = gql_ctx
4329 .schema_cache
4330 .get()
4331 .await
4332 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4333 guard
4334 .as_ref()
4335 .and_then(|cache| {
4336 cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
4337 schema_name,
4338 table_name,
4339 ))
4340 })
4341 .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4342 };
4343 let inner = if embed_expressions.is_empty() && computed.is_empty() && renamed.is_none() {
4344 inner
4345 } else {
4346 let mut projection = renamed.unwrap_or_else(|| "src.*".to_string());
4347 for expression in &computed {
4348 projection.push_str(", ");
4349 projection.push_str(expression);
4350 }
4351 for (field_name, expression) in &embed_expressions {
4352 projection.push_str(", ");
4353 projection.push_str(expression);
4354 projection.push_str(" AS ");
4355 projection.push_str(&postrust_sql::escape_ident(field_name));
4356 }
4357 format!("SELECT {} FROM ({}) AS src", projection, inner)
4358 };
4359
4360 let sql = format!(
4361 "SELECT {} FROM ({}) t",
4362 row_json("t", &row_column_types),
4363 inner
4364 );
4365
4366 let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
4370
4371 let mut result = execute_query_on(&mut tx, &sql, &bound_values).await?;
4373
4374 if embed_expressions.is_empty() {
4376 let guard = gql_ctx
4377 .schema_cache
4378 .get()
4379 .await
4380 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4381 let cache = guard
4382 .as_ref()
4383 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4384
4385 let embed_ctx = EmbedContext {
4386 schema_cache: cache,
4387 relationships,
4388 max_rows,
4389 };
4390
4391 embed_relationships(&mut tx, &embed_ctx, type_name, ctx.field(), &mut result).await?;
4392 }
4393
4394 tx.commit().await?;
4395
4396 Ok(result)
4397}
4398
4399fn rows_as_field_value<'a>(rows: Vec<serde_json::Value>, is_by_pk: bool) -> Option<FieldValue<'a>> {
4404 match is_by_pk {
4405 true => rows
4406 .into_iter()
4407 .next()
4408 .map(|v| FieldValue::value(json_to_value(v))),
4409 false => Some(FieldValue::list(
4410 rows.into_iter()
4411 .map(|v| FieldValue::value(json_to_value(v))),
4412 )),
4413 }
4414}
4415
4416#[allow(clippy::too_many_arguments)]
4418async fn resolve_mutation<'a>(
4419 ctx: &ResolverContext<'a>,
4420 schema_name: &str,
4421 table_name: &str,
4422 mutation_type: MutationType,
4423 pk_columns: &[(String, String)],
4424 relationships: &Arc<HashMap<String, Vec<RelationshipField>>>,
4425 type_names: &Arc<HashMap<(String, String), String>>,
4426 names: &Arc<crate::names::NameOverrides>,
4427 max_rows: Option<i64>,
4428) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4429 let pool = ctx.data::<PgPool>()?;
4430 let gql_ctx = ctx.data::<GraphQLContext>()?;
4431
4432 debug!(
4433 "Resolving mutation for table: {} type: {:?}",
4434 table_name, mutation_type
4435 );
4436
4437 let type_name = type_names
4443 .get(&(schema_name.to_string(), table_name.to_string()))
4444 .cloned()
4445 .unwrap_or_else(|| table_name.to_string());
4446
4447 let by_key = matches!(
4448 mutation_type,
4449 MutationType::InsertOne | MutationType::UpdateByPk | MutationType::DeleteByPk
4450 );
4451 let returning = if by_key {
4452 Some(ctx.field())
4453 } else {
4454 ctx.field()
4455 .selection_set()
4456 .find(|selection| selection.name() == "returning")
4457 };
4458
4459 if mutation_type == MutationType::UpdateMany {
4464 let updates = ctx
4465 .args
4466 .try_get("updates")
4467 .ok()
4468 .map(|v| accessor_to_json(&v))
4469 .unwrap_or(serde_json::Value::Null);
4470 let serde_json::Value::Array(entries) = updates else {
4471 return Err(async_graphql::Error::new(
4472 "\"updates\" takes a list of {where, _set, …} objects",
4473 ));
4474 };
4475 let column_types = column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await;
4476 let mut answers: Vec<FieldValue<'_>> = Vec::with_capacity(entries.len());
4477 for entry in entries {
4478 let serde_json::Value::Object(entry) = entry else {
4479 return Err(async_graphql::Error::new(
4480 "each update is an object of {where, _set, …}",
4481 ));
4482 };
4483 let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4484 .iter()
4485 .filter_map(|name| entry.get(*name).map(|value| (*name, value.clone())))
4486 .filter(|(_, value)| !value.is_null())
4487 .collect();
4488 let rows = execute_update(
4489 pool,
4490 gql_ctx,
4491 schema_name,
4492 table_name,
4493 &type_name,
4494 operators,
4495 column_types.clone(),
4496 entry.get("where").cloned(),
4497 relationships.as_ref(),
4498 names.as_ref(),
4499 )
4500 .await?;
4501 let affected = rows.len();
4502 let rows = match returning {
4503 Some(returning) if !rows.is_empty() => {
4504 reread_returning(
4505 pool,
4506 gql_ctx,
4507 schema_name,
4508 table_name,
4509 &type_name,
4510 rows,
4511 returning,
4512 relationships.as_ref(),
4513 names.as_ref(),
4514 max_rows,
4515 )
4516 .await?
4517 }
4518 _ => rows,
4519 };
4520 answers.extend(mutation_result(rows, affected, false));
4521 }
4522 return Ok(Some(FieldValue::list(answers)));
4523 }
4524
4525 let (result, affected) = match mutation_type {
4526 MutationType::Insert | MutationType::InsertOne => {
4527 let objects = ctx
4528 .args
4529 .try_get("objects")
4530 .ok()
4531 .map(|v| accessor_to_json(&v))
4532 .or_else(|| {
4533 ctx.args
4536 .try_get("object")
4537 .ok()
4538 .map(|v| serde_json::Value::Array(vec![accessor_to_json(&v)]))
4539 })
4540 .unwrap_or_else(|| serde_json::Value::Array(vec![]));
4541
4542 let guard = gql_ctx
4543 .schema_cache
4544 .get()
4545 .await
4546 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4547 let cache = guard
4548 .as_ref()
4549 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4550 let here = format!(
4554 "$.selectionSet.{}",
4555 ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4556 );
4557 let named = match mutation_type {
4560 MutationType::InsertOne => "object",
4561 _ => "objects",
4562 };
4563 let context = InsertContext {
4564 on_conflict: ctx
4565 .args
4566 .try_get("on_conflict")
4567 .ok()
4568 .map(|v| accessor_to_json(&v))
4569 .filter(|v| !v.is_null()),
4570 cache,
4571 relationships: relationships.as_ref(),
4572 type_names: type_names.as_ref(),
4573 names: names.as_ref(),
4574 caller: gql_ctx.caller(),
4575 row: String::new(),
4578 objects: format!("{}.args.{}", here, named),
4579 conflict: format!("{}.args.on_conflict", here),
4580 };
4581
4582 execute_insert(
4583 pool,
4584 gql_ctx,
4585 schema_name,
4586 table_name,
4587 objects,
4588 &context,
4589 mutation_type == MutationType::InsertOne,
4590 )
4591 .await?
4592 }
4593 MutationType::UpdateMany | MutationType::Update | MutationType::UpdateByPk => {
4596 let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4600 .iter()
4601 .filter_map(|name| {
4602 ctx.args
4603 .try_get(name)
4604 .ok()
4605 .map(|v| (*name, accessor_to_json(&v)))
4606 })
4607 .filter(|(_, value)| !value.is_null())
4608 .collect();
4609
4610 let where_clause = if mutation_type == MutationType::UpdateByPk {
4611 Some(pk_where_from_args(
4612 ctx,
4613 schema_name,
4614 table_name,
4615 pk_columns,
4616 names.as_ref(),
4617 )?)
4618 } else {
4619 ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4620 };
4621 let where_clause = and_predicates(
4625 where_clause,
4626 permission_filter(
4627 &gql_ctx.caller(),
4628 names.as_ref(),
4629 schema_name,
4630 table_name,
4631 crate::role::Verb::Update,
4632 )?,
4633 );
4634
4635 execute_update(
4636 pool,
4637 gql_ctx,
4638 schema_name,
4639 table_name,
4640 &type_name,
4641 operators,
4642 column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await,
4643 where_clause,
4644 relationships.as_ref(),
4645 names.as_ref(),
4646 )
4647 .await
4648 .map(|rows| {
4649 let count = rows.len();
4650 (rows, count)
4651 })?
4652 }
4653 MutationType::Delete | MutationType::DeleteByPk => {
4654 let where_clause = if mutation_type == MutationType::DeleteByPk {
4655 Some(pk_where_from_args(
4656 ctx,
4657 schema_name,
4658 table_name,
4659 pk_columns,
4660 names.as_ref(),
4661 )?)
4662 } else {
4663 ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4664 };
4665 let where_clause = and_predicates(
4666 where_clause,
4667 permission_filter(
4668 &gql_ctx.caller(),
4669 names.as_ref(),
4670 schema_name,
4671 table_name,
4672 crate::role::Verb::Delete,
4673 )?,
4674 );
4675
4676 execute_delete(
4677 pool,
4678 gql_ctx,
4679 schema_name,
4680 table_name,
4681 &type_name,
4682 where_clause,
4683 returning,
4684 relationships.as_ref(),
4685 names.as_ref(),
4686 max_rows,
4687 )
4688 .await
4689 .map(|rows| {
4690 let count = rows.len();
4691 (rows, count)
4692 })?
4693 }
4694 };
4695
4696 let is_delete = matches!(
4697 mutation_type,
4698 MutationType::Delete | MutationType::DeleteByPk
4699 );
4700 let result = match returning {
4701 Some(returning) if !result.is_empty() && !is_delete => {
4702 reread_returning(
4703 pool,
4704 gql_ctx,
4705 schema_name,
4706 table_name,
4707 &type_name,
4708 result,
4709 returning,
4710 relationships.as_ref(),
4711 names.as_ref(),
4712 max_rows,
4713 )
4714 .await?
4715 }
4716 _ => result,
4717 };
4718
4719 Ok(mutation_result(result, affected, by_key))
4720}
4721
4722async fn write_tx<'a>(
4730 gql_ctx: &'a GraphQLContext,
4731 pool: &PgPool,
4732) -> Result<
4733 tokio::sync::MutexGuard<'a, Option<sqlx::Transaction<'static, sqlx::Postgres>>>,
4734 async_graphql::Error,
4735> {
4736 let mut guard = gql_ctx.write.lock().await;
4737 if guard.is_none() {
4738 *guard = Some(begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?);
4739 }
4740 Ok(guard)
4741}
4742
4743async fn begin_with_session(
4749 pool: &PgPool,
4750 role: &str,
4751 settings: &[(String, String)],
4752) -> Result<sqlx::Transaction<'static, sqlx::Postgres>, async_graphql::Error> {
4753 let mut tx = pool.begin().await?;
4754
4755 if !settings.is_empty() {
4765 let calls = (0..settings.len())
4766 .map(|i| format!("set_config(${}, ${}, true)", i * 2 + 1, i * 2 + 2))
4767 .collect::<Vec<_>>()
4768 .join(", ");
4769 let sql = format!("SELECT {calls}");
4770 let mut query = sqlx::query_scalar::<_, String>(&sql);
4771 for (name, value) in settings {
4772 query = query.bind(name).bind(value);
4773 }
4774 query.fetch_optional(&mut *tx).await?;
4775 }
4776
4777 sqlx::query(&format!(
4778 "SET LOCAL ROLE {}",
4779 postrust_sql::escape_ident(role)
4780 ))
4781 .execute(&mut *tx)
4782 .await?;
4783 Ok(tx)
4784}
4785
4786fn row_json(expr: &str, column_types: &HashMap<String, String>) -> String {
4797 let mut shapes: Vec<(&String, &String)> = column_types
4798 .iter()
4799 .filter(|(_, pg_type)| matches!(pg_type.as_str(), "geometry" | "geography"))
4800 .collect();
4801 if shapes.is_empty() {
4802 return format!("row_to_json({})", expr);
4803 }
4804 shapes.sort();
4805
4806 let overrides: Vec<String> = shapes
4807 .iter()
4808 .map(|(name, _)| {
4809 format!(
4810 "'{}', ST_AsGeoJSON({}.{}, 9, 4)::jsonb",
4816 name.replace('\'', "''"),
4817 expr,
4818 postrust_sql::escape_ident(name)
4819 )
4820 })
4821 .collect();
4822 format!(
4823 "(to_jsonb({}) || jsonb_build_object({}))",
4824 expr,
4825 overrides.join(", ")
4826 )
4827}
4828
4829async fn execute_query_on(
4832 conn: &mut sqlx::PgConnection,
4833 sql: &str,
4834 params: &[serde_json::Value],
4835) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4836 use sqlx::Row;
4837
4838 trace!("Executing SQL: {}", sql);
4839
4840 let mut query = sqlx::query(sql);
4842 for param in params {
4843 query = bind_json_value(query, param);
4844 }
4845 let rows = query.fetch_all(&mut *conn).await.map_err(database_error)?;
4846
4847 let results: Vec<serde_json::Value> = rows
4850 .into_iter()
4851 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
4852 .collect();
4853
4854 Ok(results)
4855}
4856
4857fn check_geojson(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4870 fn refuse<T>(message: &str) -> Result<T, async_graphql::Error> {
4871 Err(coded_error("parse-failed", message))
4872 }
4873
4874 fn position(
4875 value: &serde_json::Value,
4876 ) -> Result<&Vec<serde_json::Value>, async_graphql::Error> {
4877 match value.as_array() {
4878 Some(items) if items.len() >= 2 && items.iter().all(|i| i.is_number()) => Ok(items),
4879 _ => refuse("A Position needs at least 2 elements"),
4880 }
4881 }
4882
4883 fn positions(
4884 value: Option<&serde_json::Value>,
4885 least: usize,
4886 what: &str,
4887 ) -> Result<Vec<Vec<serde_json::Value>>, async_graphql::Error> {
4888 let items = match value.and_then(|v| v.as_array()) {
4889 Some(items) => items,
4890 None => return refuse(&format!("A {} needs at least {} Positions", what, least)),
4891 };
4892 if items.len() < least {
4893 return refuse(&format!("A {} needs at least {} Positions", what, least));
4894 }
4895 items.iter().map(|item| position(item).cloned()).collect()
4896 }
4897
4898 fn ring(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4899 let points = positions(Some(value), 4, "LinearRing")?;
4900 match (points.first(), points.last()) {
4901 (Some(first), Some(last)) if first == last => Ok(()),
4902 _ => refuse("the first and last locations have to be equal for a LinearRing"),
4903 }
4904 }
4905
4906 fn polygon(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4907 let Some(rings) = value.as_array() else {
4908 return refuse("A LinearRing needs at least 4 Positions");
4909 };
4910 rings.iter().try_for_each(ring)
4911 }
4912
4913 let serde_json::Value::Object(map) = value else {
4914 return Ok(());
4915 };
4916 let Some(kind) = map.get("type").and_then(|v| v.as_str()) else {
4917 return Ok(());
4918 };
4919 let coordinates = map.get("coordinates");
4920 match kind {
4921 "Point" => {
4922 position(coordinates.unwrap_or(&serde_json::Value::Null))?;
4923 }
4924 "MultiPoint" => {
4925 positions(coordinates, 0, "MultiPoint")?;
4926 }
4927 "LineString" => {
4928 positions(coordinates, 2, "LineString")?;
4929 }
4930 "MultiLineString" => {
4931 for line in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4932 positions(Some(line), 2, "LineString")?;
4933 }
4934 }
4935 "Polygon" => polygon(coordinates.unwrap_or(&serde_json::Value::Null))?,
4936 "MultiPolygon" => {
4937 for one in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4938 polygon(one)?;
4939 }
4940 }
4941 "GeometryCollection" => {
4942 for geometry in map
4943 .get("geometries")
4944 .and_then(|v| v.as_array())
4945 .into_iter()
4946 .flatten()
4947 {
4948 check_geojson(geometry)?;
4949 }
4950 }
4951 other => return refuse(&format!("unexpected geometry type: {}", other)),
4952 }
4953 Ok(())
4954}
4955
4956fn computed_call(
4964 definition: &postrust_core::schema_cache::ComputedColumn,
4965 row: &str,
4966 row_by_name: &str,
4967) -> String {
4968 let function = format!(
4969 "{}.{}",
4970 postrust_sql::escape_ident(&definition.function.schema),
4971 postrust_sql::escape_ident(&definition.function.name)
4972 );
4973 match (&definition.session_argument, &definition.row_argument) {
4974 (Some(session), Some(row_argument)) => format!(
4975 "{}({} => {}, {} => {})",
4976 function,
4977 postrust_sql::escape_ident(row_argument),
4978 row_by_name,
4979 postrust_sql::escape_ident(session),
4980 SESSION_ARGUMENT
4981 ),
4982 _ => format!("{}({})", function, row),
4983 }
4984}
4985
4986const SESSION_ARGUMENT: &str = "coalesce(current_setting('hasura.session', true), '{}')::json";
4992
4993const UPDATE_OPERATORS: [&str; 7] = [
4996 "_set",
4997 "_inc",
4998 "_append",
4999 "_prepend",
5000 "_delete_key",
5001 "_delete_elem",
5002 "_delete_at_path",
5003];
5004
5005const READ_ROW: &str = "pgrst_src";
5011
5012const WRITTEN_ROW: &str = "pgrst_row";
5017
5018fn write_cast(column_types: &HashMap<String, String>, column: &str) -> String {
5026 match column_types.get(column) {
5027 Some(pg_type) => format!("::{}", pg_type),
5028 None => String::new(),
5029 }
5030}
5031
5032fn write_operand(
5039 column_types: &HashMap<String, String>,
5040 column: &str,
5041 value: &serde_json::Value,
5042) -> serde_json::Value {
5043 match column_types.get(column).map(String::as_str) {
5044 Some("json") | Some("jsonb") => match value {
5045 serde_json::Value::String(_)
5046 | serde_json::Value::Number(_)
5047 | serde_json::Value::Bool(_) => serde_json::Value::String(value.to_string()),
5048 other => other.clone(),
5049 },
5050 Some(pg_type) if is_array_type(pg_type) => match value {
5055 serde_json::Value::Array(_) => serde_json::Value::String(array_literal(value)),
5056 other => other.clone(),
5057 },
5058 _ => value.clone(),
5059 }
5060}
5061
5062fn is_array_type(pg_type: &str) -> bool {
5067 pg_type.ends_with("[]") || pg_type.starts_with('_')
5068}
5069
5070fn array_literal(value: &serde_json::Value) -> String {
5076 fn element(value: &serde_json::Value) -> String {
5077 match value {
5078 serde_json::Value::Null => "NULL".to_string(),
5079 serde_json::Value::Array(_) => array_literal(value),
5080 serde_json::Value::String(text) => {
5081 format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
5082 }
5083 other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
5084 }
5085 }
5086 let items = match value {
5087 serde_json::Value::Array(items) => items,
5088 other => return element(other),
5089 };
5090 format!(
5091 "{{{}}}",
5092 items.iter().map(element).collect::<Vec<_>>().join(",")
5093 )
5094}
5095
5096fn write_expression(
5107 column_types: &HashMap<String, String>,
5108 column: &str,
5109 value: &serde_json::Value,
5110 placeholder: &str,
5111) -> String {
5112 let pg_type = column_types.get(column).map(String::as_str);
5113 if value.is_object() && matches!(pg_type, Some("geometry") | Some("geography")) {
5114 return match pg_type {
5115 Some("geography") => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
5116 _ => format!("ST_GeomFromGeoJSON({})", placeholder),
5117 };
5118 }
5119 if pg_type == Some("money") && value.is_number() {
5125 return format!("{}::numeric::money", placeholder);
5126 }
5127 format!("{}{}", placeholder, write_cast(column_types, column))
5128}
5129
5130async fn column_types_of(
5132 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
5133 schema_name: &str,
5134 table_name: &str,
5135) -> HashMap<String, String> {
5136 let Ok(guard) = schema_cache.get().await else {
5137 return HashMap::new();
5138 };
5139 let Some(cache) = guard.as_ref() else {
5140 return HashMap::new();
5141 };
5142 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5143 match cache.get_table(&qi) {
5144 Some(table) => table
5145 .columns
5146 .values()
5147 .map(|c| (c.name.clone(), c.nominal_type.clone()))
5148 .collect(),
5149 None => HashMap::new(),
5150 }
5151}
5152
5153fn insert_row<'life>(
5171 conn: &'life mut sqlx::PgConnection,
5172 schema_name: &'life str,
5173 table_name: &'life str,
5174 object: serde_json::Map<String, serde_json::Value>,
5175 context: &'life InsertContext<'life>,
5176 written_count: &'life mut usize,
5177) -> std::pin::Pin<
5178 Box<
5179 dyn std::future::Future<Output = Result<serde_json::Value, async_graphql::Error>>
5180 + Send
5181 + 'life,
5182 >,
5183> {
5184 Box::pin(async move {
5185 use sqlx::Row;
5186
5187 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5188 let table = context.cache.get_table(&qi).ok_or_else(|| {
5189 async_graphql::Error::new(format!("unknown table \"{}\"", table_name))
5190 })?;
5191 let column_types: HashMap<String, String> = table
5192 .columns
5193 .values()
5194 .map(|c| (c.name.clone(), c.nominal_type.clone()))
5195 .collect();
5196
5197 let type_name = context
5199 .type_names
5200 .get(&(schema_name.to_string(), table_name.to_string()));
5201 let relationships: &[RelationshipField] = type_name
5202 .and_then(|name| context.relationships.get(name))
5203 .map(|r| r.as_slice())
5204 .unwrap_or(&[]);
5205
5206 let mut columns = serde_json::Map::new();
5207 type Nested<'r> = (
5208 &'r RelationshipField,
5209 serde_json::Value,
5210 Option<serde_json::Value>,
5211 );
5212 let mut to_one: Vec<Nested> = Vec::new();
5213 let mut to_many: Vec<Nested> = Vec::new();
5214
5215 for (key, value) in object {
5216 match relationships.iter().find(|r| r.name == key) {
5217 None => {
5218 columns.insert(
5221 table_column_for(context.names, table, &key).to_string(),
5222 value,
5223 );
5224 }
5225 Some(_) if value.is_null() => {
5226 continue;
5230 }
5231 Some(relationship) => {
5232 let (data, conflict) = match &value {
5236 serde_json::Value::Object(map) => (
5237 map.get("data").cloned().unwrap_or(value.clone()),
5238 map.get("on_conflict").cloned().filter(|v| !v.is_null()),
5239 ),
5240 other => (other.clone(), None),
5241 };
5242 if child_holds_the_key(&relationship.relationship) {
5249 to_many.push((relationship, data, conflict));
5250 } else {
5251 to_one.push((relationship, data, conflict));
5252 }
5253 }
5254 }
5255 }
5256
5257 for (column, value) in &columns {
5260 if matches!(
5261 column_types.get(column).map(String::as_str),
5262 Some("geometry") | Some("geography")
5263 ) {
5264 check_geojson(value).map_err(|error| {
5267 let field = context
5268 .names
5269 .column(schema_name, table_name, column)
5270 .unwrap_or(column);
5271 at_path(error, &format!("{}.{}", context.row, field))
5272 })?;
5273 }
5274 }
5275
5276 for (relationship, data, conflict) in to_one {
5279 let plan =
5280 postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5281 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5282 let serde_json::Value::Object(child) = data else {
5283 return Err(async_graphql::Error::new(format!(
5284 "\"{}\" takes an object to insert",
5285 relationship.name
5286 )));
5287 };
5288 for (local, _) in &plan.columns {
5292 if columns.get(local).is_some_and(|v| !v.is_null()) {
5293 return Err(at_path(
5294 validation_error(format!(
5295 "cannot insert object relationship \"{}\" as \"{}\" column \
5296 values are already determined",
5297 relationship.name, local
5298 )),
5299 &format!("{}.{}", context.row, relationship.name),
5300 ));
5301 }
5302 }
5303 let under = format!("{}.{}", context.row, relationship.name);
5306 let nested = InsertContext {
5307 on_conflict: conflict,
5308 cache: context.cache,
5309 relationships: context.relationships,
5310 type_names: context.type_names,
5311 names: context.names,
5312 caller: context.caller,
5313 row: format!("{}.data", under),
5314 objects: format!("{}.data", under),
5315 conflict: format!("{}.on_conflict", under),
5316 };
5317 let written = insert_row(
5318 conn,
5319 &plan.foreign_schema,
5320 &plan.foreign_table,
5321 child,
5322 &nested,
5323 written_count,
5324 )
5325 .await?;
5326 for (local, foreign) in &plan.columns {
5327 if let Some(value) = written.get(foreign) {
5328 columns.insert(local.clone(), value.clone());
5329 }
5330 }
5331 }
5332
5333 for (column, value) in crate::role::presets(
5339 &context.caller,
5340 context.names,
5341 schema_name,
5342 table_name,
5343 crate::role::Verb::Insert,
5344 )
5345 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?
5346 {
5347 columns.insert(column, value);
5348 }
5349
5350 let column_names: Vec<&str> = columns.keys().map(|k| k.as_str()).collect();
5356 let mut conflict_values: Vec<serde_json::Value> = Vec::new();
5357 let conflict_sql = match &context.on_conflict {
5358 Some(serde_json::Value::Object(spec)) => {
5359 let constraint = spec.get("constraint").and_then(|v| v.as_str());
5360 let Some(constraint) = constraint else {
5361 return Err(async_graphql::Error::new(
5362 "on_conflict needs a constraint to resolve against",
5363 ));
5364 };
5365 if !table
5366 .unique_constraints
5367 .iter()
5368 .any(|(name, _)| name == constraint)
5369 {
5370 return Err(async_graphql::Error::new(format!(
5371 "\"{}\" is not a unique constraint of \"{}\"",
5372 constraint, table_name
5373 )));
5374 }
5375 let updates: Vec<&str> = spec
5376 .get("update_columns")
5377 .and_then(|v| v.as_array())
5378 .map(|items| items.iter().filter_map(|i| i.as_str()).collect())
5379 .unwrap_or_default();
5380 if updates.contains(&PLACEHOLDER_COLUMN) {
5384 return Err(at_path(
5385 coded_error("validation-failed", "erroneous column name"),
5386 &context.conflict,
5387 ));
5388 }
5389
5390 if updates.is_empty() {
5391 format!(
5392 " ON CONFLICT ON CONSTRAINT {} DO NOTHING",
5393 postrust_sql::escape_ident(constraint)
5394 )
5395 } else {
5396 let preset = crate::role::presets(
5402 &context.caller,
5403 context.names,
5404 schema_name,
5405 table_name,
5406 crate::role::Verb::Update,
5407 )
5408 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5409
5410 let mut param_idx = column_names.len() + 1;
5414 let mut assignments: Vec<String> =
5415 Vec::with_capacity(preset.len() + updates.len());
5416 for (column, value) in &preset {
5417 assignments.push(format!(
5418 "{} = {}",
5419 postrust_sql::escape_ident(column),
5420 write_expression(
5421 &column_types,
5422 column,
5423 value,
5424 &format!("${}", param_idx)
5425 )
5426 ));
5427 conflict_values.push(write_operand(&column_types, column, value));
5428 param_idx += 1;
5429 }
5430 for field in &updates {
5431 let column = table_column_for(context.names, table, field);
5432 if preset.iter().any(|(written, _)| written == column) {
5433 continue;
5434 }
5435 assignments.push(format!(
5436 "{} = EXCLUDED.{}",
5437 postrust_sql::escape_ident(column),
5438 postrust_sql::escape_ident(column)
5439 ));
5440 }
5441 let granted = permission_filter(
5454 &context.caller,
5455 context.names,
5456 schema_name,
5457 table_name,
5458 crate::role::Verb::Update,
5459 )?;
5460 let requested = spec.get("where").filter(|f| !f.is_null()).cloned();
5461 let condition = match and_predicates(requested, granted) {
5462 Some(filter) => {
5463 let filter = &filter;
5464 let type_name = context
5465 .type_names
5466 .get(&(schema_name.to_string(), table_name.to_string()))
5467 .map(String::as_str)
5468 .unwrap_or(table_name);
5469 let scope = WhereScope::table(
5470 schema_name,
5471 table_name,
5472 type_name,
5473 context.names,
5474 )
5475 .under_alias(WRITTEN_ROW)
5476 .with_resolution(context.cache, context.relationships);
5477 let mut alias_counter = 0usize;
5478 build_condition(
5479 filter,
5480 &scope,
5481 &mut param_idx,
5482 &mut conflict_values,
5483 &mut alias_counter,
5484 )?
5485 .map(|sql| format!(" WHERE {}", sql))
5486 .unwrap_or_default()
5487 }
5488 None => String::new(),
5489 };
5490 format!(
5491 " ON CONFLICT ON CONSTRAINT {} DO UPDATE SET {}{}",
5492 postrust_sql::escape_ident(constraint),
5493 assignments.join(", "),
5494 condition
5495 )
5496 }
5497 }
5498 _ => String::new(),
5499 };
5500
5501 let qualified_table = postrust_sql::escape_ident(WRITTEN_ROW);
5507
5508 let check_scope = WhereScope::for_alias(
5513 schema_name,
5514 table_name,
5515 WRITTEN_ROW,
5516 context
5517 .type_names
5518 .get(&(schema_name.to_string(), table_name.to_string()))
5519 .map(String::as_str)
5520 .unwrap_or(table_name),
5521 context.cache,
5522 context.relationships,
5523 context.names,
5524 );
5525 let (check_sql, check_values) = {
5526 let check = crate::role::write_check(
5527 &context.caller,
5528 context.names,
5529 schema_name,
5530 table_name,
5531 crate::role::Verb::Insert,
5532 )
5533 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5534 match check {
5535 None => (String::new(), Vec::new()),
5536 Some(check) => {
5537 let mut values = Vec::new();
5538 let mut param_idx = column_names.len() + conflict_values.len() + 1;
5539 let mut alias_counter = 0usize;
5540 match build_condition(
5541 &check,
5542 &check_scope,
5543 &mut param_idx,
5544 &mut values,
5545 &mut alias_counter,
5546 )? {
5547 None => (String::new(), Vec::new()),
5548 Some(sql) => (
5549 format!(
5550 ", ({}) AS {}",
5551 sql,
5552 postrust_sql::escape_ident(CHECK_COLUMN)
5553 ),
5554 values,
5555 ),
5556 }
5557 }
5558 }
5559 };
5560
5561 let names = column_names;
5562 let written = if names.is_empty() {
5563 let sql = format!(
5566 "INSERT INTO {}.{} AS {} DEFAULT VALUES{} RETURNING {}{}",
5567 postrust_sql::escape_ident(schema_name),
5568 postrust_sql::escape_ident(table_name),
5569 qualified_table,
5570 conflict_sql,
5571 row_json(&qualified_table, &column_types),
5572 check_sql
5573 );
5574 let mut query = sqlx::query(&sql);
5575 for value in &conflict_values {
5576 query = bind_json_value(query, value);
5577 }
5578 for value in &check_values {
5579 query = bind_json_value(query, value);
5580 }
5581 query
5582 .fetch_optional(&mut *conn)
5583 .await
5584 .map_err(|error| at_path(database_error(error), &context.objects))?
5587 } else {
5588 let placeholders: Vec<String> = names
5589 .iter()
5590 .enumerate()
5591 .map(|(i, column)| {
5592 let placeholder = format!("${}", i + 1);
5593 match columns.get(*column) {
5594 Some(value) => write_expression(&column_types, column, value, &placeholder),
5595 None => placeholder,
5596 }
5597 })
5598 .collect();
5599 let sql = format!(
5600 "INSERT INTO {}.{} AS {} ({}) VALUES ({}){} RETURNING {}{}",
5601 postrust_sql::escape_ident(schema_name),
5602 postrust_sql::escape_ident(table_name),
5603 qualified_table,
5604 names
5605 .iter()
5606 .map(|c| postrust_sql::escape_ident(c))
5607 .collect::<Vec<_>>()
5608 .join(", "),
5609 placeholders.join(", "),
5610 conflict_sql,
5611 row_json(&qualified_table, &column_types),
5612 check_sql
5613 );
5614 trace!("Executing INSERT SQL: {}", sql);
5615 let mut query = sqlx::query(&sql);
5616 for column in &names {
5617 if let Some(value) = columns.get(*column) {
5618 query = bind_json_value(query, &write_operand(&column_types, column, value));
5619 }
5620 }
5621 for value in &conflict_values {
5622 query = bind_json_value(query, value);
5623 }
5624 for value in &check_values {
5625 query = bind_json_value(query, value);
5626 }
5627 query
5628 .fetch_optional(&mut *conn)
5629 .await
5630 .map_err(|error| at_path(database_error(error), &context.objects))?
5633 };
5634
5635 let Some(written) = written else {
5638 return Ok(serde_json::Value::Null);
5639 };
5640 refuse_unchecked_rows(std::slice::from_ref(&written), !check_sql.is_empty())
5641 .map_err(|error| at_path(error, &context.objects))?;
5642 let row: serde_json::Value = written.try_get(0).unwrap_or(serde_json::Value::Null);
5643 *written_count += 1;
5644
5645 for (relationship, data, conflict) in to_many {
5647 let plan =
5648 postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5649 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5650 let children = match data {
5651 serde_json::Value::Array(items) => items,
5652 serde_json::Value::Object(map) => vec![serde_json::Value::Object(map)],
5653 serde_json::Value::Null => Vec::new(),
5654 _ => {
5655 return Err(async_graphql::Error::new(format!(
5656 "\"{}\" takes objects to insert",
5657 relationship.name
5658 )))
5659 }
5660 };
5661 for (child_index, child) in children.into_iter().enumerate() {
5662 let serde_json::Value::Object(mut child) = child else {
5663 continue;
5664 };
5665 for (local, foreign) in &plan.columns {
5666 if child.get(foreign).is_some_and(|v| !v.is_null()) {
5670 return Err(at_path(
5671 validation_error(format!(
5672 "cannot insert \"{}\" columns as their values are already \
5673 being determined by parent insert",
5674 foreign
5675 )),
5676 &format!(
5677 "{}.{}{}",
5678 context.row,
5679 relationship.name,
5680 match relationship.is_list {
5681 true => format!(".data[{}]", child_index),
5682 false => ".data".to_string(),
5683 }
5684 ),
5685 ));
5686 }
5687 if let Some(value) = row.get(local) {
5688 child.insert(foreign.clone(), value.clone());
5689 }
5690 }
5691 let at = match relationship.is_list {
5697 true => format!(".data[{}]", child_index),
5698 false => ".data".to_string(),
5699 };
5700 let under = format!("{}.{}", context.row, relationship.name);
5701 let nested = InsertContext {
5702 on_conflict: conflict.clone(),
5703 cache: context.cache,
5704 relationships: context.relationships,
5705 type_names: context.type_names,
5706 names: context.names,
5707 caller: context.caller,
5708 row: format!("{}{}", under, at),
5709 objects: format!("{}.data", under),
5710 conflict: format!("{}.on_conflict", under),
5711 };
5712 insert_row(
5713 conn,
5714 &plan.foreign_schema,
5715 &plan.foreign_table,
5716 child,
5717 &nested,
5718 written_count,
5719 )
5720 .await?;
5721 }
5722 }
5723
5724 Ok(row)
5725 })
5726}
5727
5728#[derive(Clone)]
5734struct InsertContext<'a> {
5735 on_conflict: Option<serde_json::Value>,
5738 cache: &'a SchemaCache,
5739 relationships: &'a HashMap<String, Vec<RelationshipField>>,
5740 type_names: &'a HashMap<(String, String), String>,
5744 names: &'a crate::names::NameOverrides,
5747 caller: crate::role::Caller<'a>,
5751 row: String,
5756 objects: String,
5760 conflict: String,
5762}
5763
5764#[allow(clippy::too_many_arguments)]
5777async fn reread_returning(
5778 pool: &PgPool,
5779 gql_ctx: &GraphQLContext,
5780 schema_name: &str,
5781 table_name: &str,
5782 type_name: &str,
5783 rows: Vec<Value>,
5784 returning: async_graphql::SelectionField<'_>,
5785 relationships: &HashMap<String, Vec<RelationshipField>>,
5786 names: &crate::names::NameOverrides,
5787 max_rows: Option<i64>,
5788) -> Result<Vec<Value>, async_graphql::Error> {
5789 let guard = gql_ctx
5790 .schema_cache
5791 .get()
5792 .await
5793 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5794 let Some(cache) = guard.as_ref() else {
5795 return Ok(rows);
5796 };
5797 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5798 let Some(table) = cache.get_table(&qi) else {
5799 return Ok(rows);
5800 };
5801 if table.pk_cols.is_empty() {
5802 return Ok(rows);
5804 }
5805
5806 let mut param_idx = 1usize;
5807 let mut values: Vec<serde_json::Value> = Vec::new();
5808 let mut alias_counter = 0usize;
5809 let embeds = build_embed_expressions(
5810 &gql_ctx.caller(),
5811 cache,
5812 relationships,
5813 type_name,
5814 "src",
5815 returning.selection_set(),
5816 max_rows,
5817 &mut alias_counter,
5818 &mut param_idx,
5819 &mut values,
5820 names,
5821 )?;
5822 let computed = computed_projections(
5823 table,
5824 returning.selection_set(),
5825 "src",
5826 names,
5827 cache,
5828 &mut param_idx,
5829 &mut values,
5830 )?;
5831 let renames = names.renames_columns(&table.schema, &table.name);
5835 if embeds.is_empty() && computed.is_empty() && !renames {
5836 return Ok(rows);
5837 }
5838
5839 let mut conditions = Vec::with_capacity(rows.len());
5841 for row in &rows {
5842 let mut parts = Vec::with_capacity(table.pk_cols.len());
5843 for column in &table.pk_cols {
5844 let Value::Object(map) = row else {
5845 return Ok(rows);
5846 };
5847 let Some(value) = map.get(&async_graphql::Name::new(column)) else {
5848 return Ok(rows);
5849 };
5850 parts.push(format!(
5851 "{} = ${}",
5852 postrust_sql::escape_ident(column),
5853 param_idx
5854 ));
5855 values.push(value_to_json(value));
5856 param_idx += 1;
5857 }
5858 conditions.push(format!("({})", parts.join(" AND ")));
5859 }
5860 if conditions.is_empty() {
5861 return Ok(rows);
5862 }
5863
5864 let mut projection =
5865 rename_projection(table, "src", names).unwrap_or_else(|| "src.*".to_string());
5866 for expression in &computed {
5867 projection.push_str(", ");
5868 projection.push_str(expression);
5869 }
5870 for (name, expression) in &embeds {
5871 projection.push_str(", ");
5872 projection.push_str(expression);
5873 projection.push_str(" AS ");
5874 projection.push_str(&postrust_sql::escape_ident(name));
5875 }
5876
5877 let sql = format!(
5878 "SELECT {} FROM (SELECT {} FROM (SELECT * FROM {}.{} WHERE {}) AS src) AS pgrst_r",
5879 row_json("pgrst_r", &exposed_column_types(table, names)),
5880 projection,
5881 postrust_sql::escape_ident(schema_name),
5882 postrust_sql::escape_ident(table_name),
5883 conditions.join(" OR ")
5884 );
5885
5886 let mut guard = write_tx(gql_ctx, pool).await?;
5889 let conn = guard.as_mut().expect("write_tx opens one");
5890 let reread = execute_query_on(conn, &sql, &values).await?;
5891 drop(guard);
5892
5893 if reread.is_empty() {
5894 return Ok(rows);
5895 }
5896 Ok(reread.into_iter().map(json_to_value).collect())
5897}
5898
5899async fn execute_insert(
5901 pool: &PgPool,
5902 gql_ctx: &GraphQLContext,
5903 schema_name: &str,
5904 table_name: &str,
5905 objects: serde_json::Value,
5906 context: &InsertContext<'_>,
5907 single: bool,
5911) -> Result<(Vec<Value>, usize), async_graphql::Error> {
5912 trace!("Insert mutation for {}: {:?}", table_name, objects);
5913
5914 let objects_array = match objects {
5916 serde_json::Value::Array(arr) => arr,
5917 serde_json::Value::Object(obj) => vec![serde_json::Value::Object(obj)],
5918 _ => {
5919 return Err(async_graphql::Error::new(
5920 "objects must be an array or object",
5921 ))
5922 }
5923 };
5924
5925 if objects_array.is_empty() {
5926 return Err(async_graphql::Error::new("objects cannot be empty"));
5927 }
5928
5929 let mut guard = write_tx(gql_ctx, pool).await?;
5930 let conn = guard.as_mut().expect("write_tx opens one");
5931
5932 let mut inserted: Vec<Value> = Vec::new();
5933 let mut written = 0usize;
5934
5935 for (index, object) in objects_array.into_iter().enumerate() {
5936 let serde_json::Value::Object(map) = object else {
5937 return Err(async_graphql::Error::new(
5938 "each object to insert is an object",
5939 ));
5940 };
5941 let context = InsertContext {
5944 row: match single {
5945 true => context.objects.clone(),
5946 false => format!("{}[{}]", context.objects, index),
5947 },
5948 ..context.clone()
5949 };
5950 let row = insert_row(conn, schema_name, table_name, map, &context, &mut written).await?;
5951 if !row.is_null() {
5955 inserted.push(json_to_value(row));
5956 }
5957 }
5958
5959 Ok((inserted, written))
5960}
5961
5962fn mutation_result<'a>(rows: Vec<Value>, affected: usize, by_key: bool) -> Option<FieldValue<'a>> {
5974 if by_key {
5975 return rows.into_iter().next().map(FieldValue::value);
5976 }
5977 let mut response = async_graphql::indexmap::IndexMap::new();
5978 response.insert(
5979 async_graphql::Name::new("affected_rows"),
5980 Value::from(affected),
5981 );
5982 response.insert(async_graphql::Name::new("returning"), Value::List(rows));
5983 Some(FieldValue::value(Value::Object(response)))
5984}
5985
5986fn bind_json_value<'q>(
5988 query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
5989 value: &serde_json::Value,
5990) -> sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments> {
5991 match value {
5992 serde_json::Value::Null => query.bind(None::<String>),
5993 serde_json::Value::Bool(b) => query.bind(*b),
5994 serde_json::Value::Number(n) => {
5995 if let Some(i) = n.as_i64() {
5996 query.bind(i)
5997 } else if let Some(f) = n.as_f64() {
5998 query.bind(f)
5999 } else {
6000 query.bind(n.to_string())
6001 }
6002 }
6003 serde_json::Value::String(s) => query.bind(s.clone()),
6004 _ => query.bind(value.to_string()),
6005 }
6006}
6007
6008#[allow(clippy::too_many_arguments)] async fn execute_update(
6011 pool: &PgPool,
6012 gql_ctx: &GraphQLContext,
6013 schema_name: &str,
6014 table_name: &str,
6015 type_name: &str,
6016 operators: Vec<(&'static str, serde_json::Value)>,
6017 column_types: HashMap<String, String>,
6018 where_clause: Option<serde_json::Value>,
6019 relationships: &HashMap<String, Vec<RelationshipField>>,
6020 names: &crate::names::NameOverrides,
6021) -> Result<Vec<Value>, async_graphql::Error> {
6022 use sqlx::Row;
6023
6024 trace!("Update mutation for {}: {:?}", table_name, operators);
6025
6026 let mut set_parts: Vec<String> = Vec::new();
6031 let mut set_values: Vec<serde_json::Value> = Vec::new();
6032 let mut param_idx = 1;
6033 let mut written: HashSet<String> = HashSet::new();
6034
6035 let preset = crate::role::presets(
6045 &gql_ctx.caller(),
6046 names,
6047 schema_name,
6048 table_name,
6049 crate::role::Verb::Update,
6050 )
6051 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6052 let preset_columns: HashSet<&str> = preset.iter().map(|(name, _)| name.as_str()).collect();
6053 for (column, value) in &preset {
6054 let placeholder =
6055 write_expression(&column_types, column, value, &format!("${}", param_idx));
6056 set_parts.push(format!(
6057 "{} = {}",
6058 postrust_sql::escape_ident(column),
6059 placeholder
6060 ));
6061 set_values.push(write_operand(&column_types, column, value));
6062 param_idx += 1;
6063 }
6064
6065 for (operator, payload) in &operators {
6066 let serde_json::Value::Object(map) = payload else {
6067 return Err(async_graphql::Error::new(format!(
6068 "\"{}\" takes an object mapping columns to values",
6069 operator
6070 )));
6071 };
6072 for (field, value) in map {
6073 let column = &names
6074 .column_source(schema_name, table_name, field)
6075 .unwrap_or(field)
6076 .to_string();
6077 if preset_columns.contains(column.as_str()) {
6080 continue;
6081 }
6082 if !written.insert(column.clone()) {
6083 return Err(async_graphql::Error::new(format!(
6084 "\"{}\" is written twice in one update; a column may be \
6085 changed by one operator at a time",
6086 column
6087 )));
6088 }
6089 let quoted = postrust_sql::escape_ident(column);
6090 let placeholder = match *operator {
6099 "_set" | "_inc" => {
6100 write_expression(&column_types, column, value, &format!("${}", param_idx))
6101 }
6102 _ => format!("${}", param_idx),
6103 };
6104 let assignment = match *operator {
6107 "_set" => format!("{} = {}", quoted, placeholder),
6108 "_inc" => format!("{} = {} + {}", quoted, quoted, placeholder),
6109 "_append" => format!("{} = {} || {}::jsonb", quoted, quoted, placeholder),
6110 "_prepend" => format!("{} = {}::jsonb || {}", quoted, placeholder, quoted),
6111 "_delete_key" => format!("{} = {} - {}::text", quoted, quoted, placeholder),
6112 "_delete_elem" => format!("{} = {} - {}::int", quoted, quoted, placeholder),
6113 "_delete_at_path" => {
6119 let Some(steps) = value.as_array() else {
6120 return Err(async_graphql::Error::new(format!(
6121 "\"_delete_at_path\" on \"{}\" takes a list of keys",
6122 column
6123 )));
6124 };
6125 let path = sql_array(steps, "text[]", &mut param_idx, &mut set_values);
6126 set_parts.push(format!("{} = {} #- {}", quoted, quoted, path));
6127 continue;
6128 }
6129 other => {
6130 return Err(async_graphql::Error::new(format!(
6131 "unsupported update operator \"{}\"",
6132 other
6133 )))
6134 }
6135 };
6136 if *operator == "_set"
6137 && matches!(
6138 column_types.get(column).map(String::as_str),
6139 Some("geometry") | Some("geography")
6140 )
6141 {
6142 check_geojson(value)?;
6143 }
6144 set_parts.push(assignment);
6145 set_values.push(match *operator {
6146 "_set" => write_operand(&column_types, column, value),
6147 _ => value.clone(),
6148 });
6149 param_idx += 1;
6150 }
6151 }
6152
6153 if set_parts.is_empty() {
6158 return Ok(Vec::new());
6159 }
6160
6161 let guard = gql_ctx
6165 .schema_cache
6166 .get()
6167 .await
6168 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6169 let cache = guard
6170 .as_ref()
6171 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6172 let scope = WhereScope::table(schema_name, table_name, type_name, names)
6173 .under_alias(WRITTEN_ROW)
6174 .with_resolution(cache, relationships);
6175 let (where_sql, where_values) = build_where_clause(where_clause.as_ref(), param_idx, &scope)?;
6176
6177 if where_sql.is_empty() {
6180 return Err(async_graphql::Error::new(format!(
6181 "update on \"{}\" requires a `where` argument with at least one \
6182 recognised condition; refusing to update every row",
6183 table_name
6184 )));
6185 }
6186
6187 let (check_sql, check_values) = permission_check_sql(
6192 gql_ctx,
6193 names,
6194 schema_name,
6195 table_name,
6196 crate::role::Verb::Update,
6197 &scope,
6198 param_idx + where_values.len(),
6199 )?;
6200
6201 let sql = format!(
6202 "UPDATE {}.{} AS {} SET {} {} RETURNING {}{}",
6203 postrust_sql::escape_ident(schema_name),
6204 postrust_sql::escape_ident(table_name),
6205 postrust_sql::escape_ident(WRITTEN_ROW),
6206 set_parts.join(", "),
6207 where_sql,
6208 row_json(&postrust_sql::escape_ident(WRITTEN_ROW), &column_types),
6209 check_sql
6210 );
6211
6212 trace!("Executing UPDATE SQL: {}", sql);
6213
6214 let mut query = sqlx::query(&sql);
6216
6217 for val in &set_values {
6219 query = bind_json_value(query, val);
6220 }
6221
6222 for val in &where_values {
6224 query = bind_json_value(query, val);
6225 }
6226
6227 for val in &check_values {
6229 query = bind_json_value(query, val);
6230 }
6231
6232 let mut guard = write_tx(gql_ctx, pool).await?;
6233 let conn = guard.as_mut().expect("write_tx opens one");
6234 let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6235
6236 refuse_unchecked_rows(&rows, !check_sql.is_empty())?;
6237
6238 let updated: Vec<Value> = rows
6239 .iter()
6240 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6241 .map(json_to_value)
6242 .collect();
6243
6244 Ok(updated)
6245}
6246
6247fn permission_check_sql(
6254 gql_ctx: &GraphQLContext,
6255 names: &crate::names::NameOverrides,
6256 schema_name: &str,
6257 table_name: &str,
6258 verb: crate::role::Verb,
6259 scope: &WhereScope<'_>,
6260 start_param_idx: usize,
6261) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6262 let check = crate::role::write_check(&gql_ctx.caller(), names, schema_name, table_name, verb)
6263 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6264 let Some(check) = check else {
6265 return Ok((String::new(), Vec::new()));
6266 };
6267
6268 let mut values = Vec::new();
6269 let mut param_idx = start_param_idx;
6270 let mut alias_counter = 0usize;
6271 let condition = build_condition(
6272 &check,
6273 scope,
6274 &mut param_idx,
6275 &mut values,
6276 &mut alias_counter,
6277 )?;
6278 Ok(match condition {
6279 None => (String::new(), Vec::new()),
6282 Some(sql) => (
6283 format!(
6284 ", ({}) AS {}",
6285 sql,
6286 postrust_sql::escape_ident(CHECK_COLUMN)
6287 ),
6288 values,
6289 ),
6290 })
6291}
6292
6293const CHECK_COLUMN: &str = "pgrst_permission_check";
6295
6296fn refuse_unchecked_rows(
6303 rows: &[sqlx::postgres::PgRow],
6304 checked: bool,
6305) -> Result<(), async_graphql::Error> {
6306 if !checked {
6307 return Ok(());
6308 }
6309 use sqlx::Row;
6310 let all_passed = rows
6311 .iter()
6312 .all(|row| row.try_get::<bool, _>(CHECK_COLUMN).unwrap_or(false));
6313 match all_passed {
6314 true => Ok(()),
6315 false => Err(coded_error("permission-error", crate::role::CHECK_FAILED)),
6316 }
6317}
6318
6319#[allow(clippy::too_many_arguments)] async fn execute_delete(
6322 pool: &PgPool,
6323 gql_ctx: &GraphQLContext,
6324 schema_name: &str,
6325 table_name: &str,
6326 type_name: &str,
6327 where_clause: Option<serde_json::Value>,
6328 returning: Option<async_graphql::SelectionField<'_>>,
6329 relationships: &HashMap<String, Vec<RelationshipField>>,
6330 names: &crate::names::NameOverrides,
6331 max_rows: Option<i64>,
6332) -> Result<Vec<Value>, async_graphql::Error> {
6333 use sqlx::Row;
6334
6335 trace!("Delete mutation for {}", table_name);
6336
6337 let guard = gql_ctx
6338 .schema_cache
6339 .get()
6340 .await
6341 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6342 let cache = guard
6343 .as_ref()
6344 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6345 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
6346 let column_types = cache
6347 .get_table(&qi)
6348 .map(|table| exposed_column_types(table, names))
6349 .unwrap_or_default();
6350
6351 let scope = WhereScope::table(schema_name, table_name, type_name, names)
6355 .under_alias(WRITTEN_ROW)
6356 .with_resolution(cache, relationships);
6357 let (where_sql, mut values) = build_where_clause(where_clause.as_ref(), 1, &scope)?;
6358
6359 if where_sql.is_empty() {
6362 return Err(async_graphql::Error::new(format!(
6363 "delete on \"{}\" requires a `where` argument with at least one \
6364 recognised condition; refusing to delete every row",
6365 table_name
6366 )));
6367 }
6368
6369 let mut projection = String::new();
6375 let renamed = cache
6376 .get_table(&qi)
6377 .and_then(|table| rename_projection(table, "src", names));
6378 if let Some(returning) = returning {
6379 let mut param_idx = values.len() + 1;
6380 let mut alias_counter = 0usize;
6381 let embeds = build_embed_expressions(
6382 &gql_ctx.caller(),
6383 cache,
6384 relationships,
6385 type_name,
6386 "src",
6387 returning.selection_set(),
6388 max_rows,
6389 &mut alias_counter,
6390 &mut param_idx,
6391 &mut values,
6392 names,
6393 )?;
6394 let computed = match cache.get_table(&qi) {
6395 Some(table) => computed_projections(
6396 table,
6397 returning.selection_set(),
6398 "src",
6399 names,
6400 cache,
6401 &mut param_idx,
6402 &mut values,
6403 )?,
6404 None => Vec::new(),
6405 };
6406 for expression in &computed {
6407 projection.push_str(", ");
6408 projection.push_str(expression);
6409 }
6410 for (name, expression) in &embeds {
6411 projection.push_str(", ");
6412 projection.push_str(expression);
6413 projection.push_str(" AS ");
6414 projection.push_str(&postrust_sql::escape_ident(name));
6415 }
6416 }
6417
6418 let written = postrust_sql::escape_ident(WRITTEN_ROW);
6419 let sql = if projection.is_empty() && renamed.is_none() {
6420 format!(
6421 "DELETE FROM {}.{} AS {} {} RETURNING {}",
6422 postrust_sql::escape_ident(schema_name),
6423 postrust_sql::escape_ident(table_name),
6424 written,
6425 where_sql,
6426 row_json(&written, &column_types)
6427 )
6428 } else {
6429 format!(
6430 "WITH pgrst_deleted AS (DELETE FROM {}.{} AS {} {} RETURNING {}.*) \
6431 SELECT {} FROM (SELECT {}{} FROM pgrst_deleted AS src) AS pgrst_r",
6432 postrust_sql::escape_ident(schema_name),
6433 postrust_sql::escape_ident(table_name),
6434 written,
6435 where_sql,
6436 written,
6437 row_json("pgrst_r", &column_types),
6438 renamed.unwrap_or_else(|| "src.*".to_string()),
6439 projection
6440 )
6441 };
6442
6443 trace!("Executing DELETE SQL: {}", sql);
6444
6445 let mut query = sqlx::query(&sql);
6446 for val in &values {
6447 query = bind_json_value(query, val);
6448 }
6449
6450 let mut guard = write_tx(gql_ctx, pool).await?;
6451 let conn = guard.as_mut().expect("write_tx opens one");
6452 let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6453
6454 let deleted: Vec<Value> = rows
6455 .iter()
6456 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6457 .map(json_to_value)
6458 .collect();
6459
6460 Ok(deleted)
6461}
6462
6463fn permission_predicate(
6471 caller: &crate::role::Caller<'_>,
6472 names: &crate::names::NameOverrides,
6473 schema: &str,
6474 table: &str,
6475) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6476 permission_filter(caller, names, schema, table, crate::role::Verb::Select)
6477}
6478
6479fn permission_filter(
6481 caller: &crate::role::Caller<'_>,
6482 names: &crate::names::NameOverrides,
6483 schema: &str,
6484 table: &str,
6485 verb: crate::role::Verb,
6486) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6487 crate::role::row_filter(caller, names, schema, table, verb)
6488 .map(|filter| filter.map(|predicate| serde_json::json!({ PERMISSION: predicate })))
6489 .map_err(|fault| coded_error(fault.code(), fault.to_string()))
6490}
6491
6492const PERMISSION: &str = "_permission";
6513
6514fn permission_limit(
6520 caller: &crate::role::Caller<'_>,
6521 names: &crate::names::NameOverrides,
6522 schema: &str,
6523 table: &str,
6524) -> Option<i64> {
6525 crate::role::read_limit(caller, names, schema, table)
6526}
6527
6528fn and_predicates(
6535 request: Option<serde_json::Value>,
6536 permission: Option<serde_json::Value>,
6537) -> Option<serde_json::Value> {
6538 match (request, permission) {
6539 (None, None) => None,
6540 (Some(only), None) | (None, Some(only)) => Some(only),
6541 (Some(request), Some(permission)) => {
6542 Some(serde_json::json!({ "_and": [request, permission] }))
6543 }
6544 }
6545}
6546
6547fn build_where_clause(
6557 where_value: Option<&serde_json::Value>,
6558 start_param_idx: usize,
6559 scope: &WhereScope<'_>,
6560) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6561 let mut values: Vec<serde_json::Value> = Vec::new();
6562 let mut param_idx = start_param_idx;
6563 let mut alias_counter = 0usize;
6564
6565 let condition = match where_value {
6566 Some(value) => build_condition(
6567 value,
6568 scope,
6569 &mut param_idx,
6570 &mut values,
6571 &mut alias_counter,
6572 )?,
6573 None => None,
6574 };
6575
6576 Ok(match condition {
6577 Some(sql) => (format!("WHERE {}", sql), values),
6578 None => (String::new(), values),
6579 })
6580}
6581
6582pub struct WhereScope<'a> {
6594 qualified: postrust_core::api_request::QualifiedIdentifier,
6596 sql_ref: String,
6598 row_ref: String,
6602 type_name: String,
6604 root_ref: String,
6612 root_qualified: postrust_core::api_request::QualifiedIdentifier,
6613 names: &'a crate::names::NameOverrides,
6616 resolution: Option<WhereResolution<'a>>,
6617 caller: Option<crate::role::Caller<'a>>,
6624}
6625
6626struct WhereResolution<'a> {
6628 cache: &'a SchemaCache,
6629 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6630}
6631
6632impl<'a> WhereScope<'a> {
6633 pub fn table(
6635 schema: &str,
6636 table: &str,
6637 type_name: &str,
6638 names: &'a crate::names::NameOverrides,
6639 ) -> Self {
6640 Self {
6641 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6642 sql_ref: format!(
6643 "{}.{}",
6644 postrust_sql::escape_ident(schema),
6645 postrust_sql::escape_ident(table)
6646 ),
6647 row_ref: postrust_sql::escape_ident(table),
6648 type_name: type_name.to_string(),
6649 root_ref: format!(
6650 "{}.{}",
6651 postrust_sql::escape_ident(schema),
6652 postrust_sql::escape_ident(table)
6653 ),
6654 root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6655 names,
6656 resolution: None,
6657 caller: None,
6658 }
6659 }
6660
6661 fn under_alias(mut self, alias: &str) -> Self {
6669 if self.root_ref == self.sql_ref {
6672 self.root_ref = postrust_sql::escape_ident(alias);
6673 }
6674 self.sql_ref = postrust_sql::escape_ident(alias);
6675 self.row_ref = postrust_sql::escape_ident(alias);
6676 self
6677 }
6678
6679 fn with_resolution(
6681 mut self,
6682 cache: &'a SchemaCache,
6683 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6684 ) -> Self {
6685 self.resolution = Some(WhereResolution {
6686 cache,
6687 relationships,
6688 });
6689 self
6690 }
6691
6692 #[allow(clippy::too_many_arguments)]
6694 fn for_alias(
6695 schema: &str,
6696 table: &str,
6697 alias: &str,
6698 type_name: &str,
6699 cache: &'a SchemaCache,
6700 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6701 names: &'a crate::names::NameOverrides,
6702 ) -> Self {
6703 Self {
6704 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6705 sql_ref: postrust_sql::escape_ident(alias),
6706 row_ref: postrust_sql::escape_ident(alias),
6707 type_name: type_name.to_string(),
6708 root_ref: postrust_sql::escape_ident(alias),
6709 root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6710 names,
6711 resolution: Some(WhereResolution {
6712 cache,
6713 relationships,
6714 }),
6715 caller: None,
6716 }
6717 }
6718
6719 fn aliased(
6726 schema: &str,
6727 table: &str,
6728 alias: &str,
6729 type_name: &str,
6730 from: &WhereScope<'a>,
6731 ) -> Self {
6732 Self {
6733 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6734 sql_ref: postrust_sql::escape_ident(alias),
6735 row_ref: postrust_sql::escape_ident(alias),
6736 type_name: type_name.to_string(),
6737 root_ref: from.root_ref.clone(),
6740 root_qualified: from.root_qualified.clone(),
6741 names: from.names,
6742 resolution: from.resolution.as_ref().map(|r| WhereResolution {
6743 cache: r.cache,
6744 relationships: r.relationships,
6745 }),
6746 caller: from.caller,
6750 }
6751 }
6752
6753 pub fn for_caller(mut self, caller: crate::role::Caller<'a>) -> Self {
6755 self.caller = Some(caller);
6756 self
6757 }
6758
6759 fn as_system(&self) -> WhereScope<'a> {
6762 WhereScope {
6763 qualified: self.qualified.clone(),
6764 sql_ref: self.sql_ref.clone(),
6765 row_ref: self.row_ref.clone(),
6766 type_name: self.type_name.clone(),
6767 root_ref: self.sql_ref.clone(),
6774 root_qualified: self.qualified.clone(),
6775 names: self.names,
6776 resolution: self.resolution.as_ref().map(|r| WhereResolution {
6777 cache: r.cache,
6778 relationships: r.relationships,
6779 }),
6780 caller: None,
6781 }
6782 }
6783
6784 fn column(&self, name: &str) -> String {
6792 let column = self.column_name(name);
6793 let plain = format!("{}.{}", self.sql_ref, postrust_sql::escape_ident(column));
6794 let Some(cache) = self.resolution.as_ref().map(|r| r.cache) else {
6795 return plain;
6796 };
6797 let Some(table) = cache.get_table(&self.qualified) else {
6798 return plain;
6799 };
6800 if table.get_column(column).is_some() {
6801 return plain;
6802 }
6803 let function = self
6804 .names
6805 .computed_source(&table.schema, &table.name, name)
6806 .unwrap_or(name);
6807 match table.get_computed_column(function) {
6808 Some(definition) => {
6809 computed_call(definition, &format!("{}.*", self.row_ref), &self.row_ref)
6810 }
6811 None => plain,
6812 }
6813 }
6814
6815 fn column_name<'n>(&'n self, name: &'n str) -> &'n str {
6817 self.names
6818 .column_source(&self.qualified.schema, &self.qualified.name, name)
6819 .unwrap_or(name)
6820 }
6821
6822 fn column_type(&self, name: &str) -> Option<String> {
6826 let cache = self.resolution.as_ref()?.cache;
6827 let table = cache.get_table(&self.qualified)?;
6828 table
6829 .get_column(self.column_name(name))
6830 .map(|c| c.nominal_type.clone())
6831 }
6832
6833 fn other_column(
6847 &self,
6848 operand: &serde_json::Value,
6849 op: &str,
6850 ) -> Result<String, async_graphql::Error> {
6851 use serde_json::Value;
6852
6853 let refused = |what: &str| {
6854 async_graphql::Error::new(format!(
6855 "the \"{}\" comparison takes the name of a column, {}",
6856 op, what
6857 ))
6858 };
6859 let (against, qualified, name) = match operand {
6860 Value::String(name) => (&self.sql_ref, &self.qualified, name.as_str()),
6861 Value::Array(path) => match path.as_slice() {
6862 [Value::String(name)] => (&self.sql_ref, &self.qualified, name.as_str()),
6863 [Value::String(root), Value::String(name)] if root == "$" => {
6864 (&self.root_ref, &self.root_qualified, name.as_str())
6865 }
6866 _ => return Err(refused("or [\"$\", \"a column\"]")),
6867 },
6868 _ => return Err(refused("written as a string")),
6869 };
6870 let column = self
6873 .names
6874 .column_source(&qualified.schema, &qualified.name, name)
6875 .unwrap_or(name);
6876 Ok(format!(
6877 "{}.{}",
6878 against,
6879 postrust_sql::escape_ident(column)
6880 ))
6881 }
6882
6883 fn relationship(&self, name: &str) -> Option<std::borrow::Cow<'a, RelationshipField>> {
6897 let resolution = self.resolution.as_ref()?;
6898 let published = resolution
6899 .relationships
6900 .get(&self.type_name)
6901 .and_then(|fields| fields.iter().find(|r| r.name == name));
6902 if let Some(found) = published {
6903 return Some(std::borrow::Cow::Borrowed(found));
6904 }
6905
6906 let table = resolution.cache.get_table(&self.qualified)?;
6907
6908 if let Some(declared) = self
6914 .names
6915 .declared_relationships(&table.schema, &table.name)
6916 {
6917 if let Some(entry) = declared.iter().find(|entry| entry.name == name) {
6918 let target = entry
6921 .target(&table.schema)
6922 .map(|(_, target)| target)
6923 .unwrap_or_default();
6924 if let Some(field) = crate::schema::mapped_relationship_field(
6925 entry,
6926 table,
6927 resolution.cache,
6928 &table.schema,
6929 &target,
6930 &target,
6931 &target,
6932 ) {
6933 return Some(std::borrow::Cow::Owned(field));
6934 }
6935 if let Some(key) = entry.using.as_deref() {
6939 if let Some(found) = resolution
6940 .cache
6941 .get_relationships(&self.qualified, &table.schema)
6942 .into_iter()
6943 .flatten()
6944 .find(|rel| {
6945 crate::schema::relationship_keys(rel)
6946 .iter()
6947 .any(|found| found == key)
6948 })
6949 {
6950 return Some(std::borrow::Cow::Owned(
6951 crate::schema::relationship::RelationshipField::from_relationship_named(
6952 found,
6953 &found.foreign_table().name,
6954 Some(&entry.name),
6955 ),
6956 ));
6957 }
6958 }
6959 }
6960 }
6961
6962 resolution
6963 .cache
6964 .get_relationships(&self.qualified, &table.schema)?
6965 .iter()
6966 .find_map(|rel| {
6967 let given = crate::schema::relationship_keys(rel)
6968 .iter()
6969 .find_map(|key| self.names.relationship(&table.schema, &table.name, key));
6970 let field = RelationshipField::from_relationship_named(
6974 rel,
6975 &rel.foreign_table().name,
6976 given,
6977 );
6978 (field.name == name).then_some(std::borrow::Cow::Owned(field))
6979 })
6980 }
6981}
6982
6983fn column_comparison(op: &str) -> Option<&'static str> {
6992 Some(match op {
6993 "_ceq" => "=",
6994 "_cne" => "<>",
6995 "_cgt" => ">",
6996 "_cgte" => ">=",
6997 "_clt" => "<",
6998 "_clte" => "<=",
6999 _ => return None,
7000 })
7001}
7002
7003fn build_condition(
7005 value: &serde_json::Value,
7006 scope: &WhereScope<'_>,
7007 param_idx: &mut usize,
7008 values: &mut Vec<serde_json::Value>,
7009 alias_counter: &mut usize,
7010) -> Result<Option<String>, async_graphql::Error> {
7011 let serde_json::Value::Object(map) = value else {
7012 return Ok(None);
7013 };
7014
7015 let mut conditions: Vec<String> = Vec::new();
7016
7017 for (key, val) in map {
7018 match key.as_str() {
7019 "_and" | "_or" => {
7023 let members = val.as_array().ok_or_else(|| {
7024 async_graphql::Error::new(format!("\"{}\" takes a list of expressions", key))
7025 })?;
7026 let mut parts = Vec::new();
7027 for member in members {
7028 if let Some(sql) =
7029 build_condition(member, scope, param_idx, values, alias_counter)?
7030 {
7031 parts.push(sql);
7032 }
7033 }
7034 if parts.is_empty() {
7035 if key == "_or" {
7036 conditions.push("false".to_string());
7037 }
7038 continue;
7039 }
7040 let joiner = if key == "_and" { " AND " } else { " OR " };
7041 conditions.push(format!("({})", parts.join(joiner)));
7042 }
7043 "_not" => {
7044 if let Some(sql) = build_condition(val, scope, param_idx, values, alias_counter)? {
7045 conditions.push(format!("NOT ({})", sql));
7046 }
7047 }
7048 PERMISSION => {
7051 if let Some(sql) =
7052 build_condition(val, &scope.as_system(), param_idx, values, alias_counter)?
7053 {
7054 conditions.push(sql);
7055 }
7056 }
7057 "_exists" => {
7061 conditions.push(table_exists_sql(
7062 val,
7063 scope,
7064 param_idx,
7065 values,
7066 alias_counter,
7067 )?);
7068 }
7069 name if scope.relationship(name).is_some() => {
7075 let relationship = scope.relationship(name).expect("just checked");
7076 conditions.push(exists_sql(
7077 &relationship,
7078 val,
7079 scope,
7080 param_idx,
7081 values,
7082 alias_counter,
7083 )?);
7084 }
7085 name if scope.column_type(name).is_none()
7090 && name
7091 .strip_suffix("_aggregate")
7092 .and_then(|rel| scope.relationship(rel))
7093 .is_some_and(|rel| rel.is_list) =>
7094 {
7095 let relationship = name
7096 .strip_suffix("_aggregate")
7097 .and_then(|rel| scope.relationship(rel))
7098 .expect("just checked");
7099 conditions.push(aggregate_predicate_sql(
7100 &relationship,
7101 val,
7102 scope,
7103 param_idx,
7104 values,
7105 alias_counter,
7106 )?);
7107 }
7108 column => {
7109 let quoted = scope.column(column);
7110 match val {
7111 serde_json::Value::Object(ops) => {
7112 let column_type = scope.column_type(column);
7113 for (op, operand) in ops {
7114 if let Some(other) = column_comparison(op) {
7120 conditions.push(format!(
7121 "{} {} {}",
7122 quoted,
7123 other,
7124 scope.other_column(operand, op)?
7125 ));
7126 continue;
7127 }
7128 conditions.push(comparison_sql(
7129 "ed,
7130 column,
7131 column_type.as_deref(),
7132 op,
7133 operand,
7134 param_idx,
7135 values,
7136 )?);
7137 }
7138 }
7139 other => {
7143 conditions.push(format!("{} = ${}", quoted, param_idx));
7144 values.push(other.clone());
7145 *param_idx += 1;
7146 }
7147 }
7148 }
7149 }
7150 }
7151
7152 Ok(if conditions.is_empty() {
7153 None
7154 } else if conditions.len() == 1 {
7155 conditions.pop()
7156 } else {
7157 Some(conditions.join(" AND "))
7158 })
7159}
7160
7161fn exists_sql(
7168 relationship: &RelationshipField,
7169 child_expression: &serde_json::Value,
7170 scope: &WhereScope<'_>,
7171 param_idx: &mut usize,
7172 values: &mut Vec<serde_json::Value>,
7173 alias_counter: &mut usize,
7174) -> Result<String, async_graphql::Error> {
7175 let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7176 async_graphql::Error::new(format!(
7177 "filtering on the relationship \"{}\" is not available here",
7178 relationship.name
7179 ))
7180 })?;
7181
7182 let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7183 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7184
7185 if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7186 return Err(async_graphql::Error::new(format!(
7187 "filtering on \"{}\" is not supported: it is reached through a junction \
7188 rather than by a key",
7189 relationship.name
7190 )));
7191 }
7192
7193 *alias_counter += 1;
7194 let alias = format!("pgrst_rel_{}", alias_counter);
7195 let child_scope = WhereScope::aliased(
7196 &plan.foreign_schema,
7197 &plan.foreign_table,
7198 &alias,
7199 &relationship.target_type,
7200 scope,
7201 );
7202
7203 let (source, mut correlation) = match &plan.function {
7206 Some(function) => (
7207 format!(
7208 "{}.{}({})",
7209 postrust_sql::escape_ident(&function.schema),
7210 postrust_sql::escape_ident(&function.name),
7211 scope.row_ref
7212 ),
7213 Vec::new(),
7214 ),
7215 None => {
7216 let mut columns = Vec::with_capacity(plan.columns.len());
7217 for (parent_column, child_column) in &plan.columns {
7218 columns.push(format!(
7219 "{} = {}",
7220 scope.column(parent_column),
7221 child_scope.column(child_column)
7222 ));
7223 }
7224 (
7225 format!(
7226 "{}.{}",
7227 postrust_sql::escape_ident(&plan.foreign_schema),
7228 postrust_sql::escape_ident(&plan.foreign_table)
7229 ),
7230 columns,
7231 )
7232 }
7233 };
7234
7235 let child_condition = build_condition(
7236 child_expression,
7237 &child_scope,
7238 param_idx,
7239 values,
7240 alias_counter,
7241 )?;
7242 if let Some(sql) = child_condition {
7243 correlation.push(format!("({})", sql));
7244 }
7245
7246 if let Some(caller) = &child_scope.caller {
7251 let permission = permission_predicate(
7258 caller,
7259 child_scope.names,
7260 &plan.foreign_schema,
7261 &plan.foreign_table,
7262 )?;
7263 if let Some(predicate) = permission {
7264 if let Some(sql) =
7265 build_condition(&predicate, &child_scope, param_idx, values, alias_counter)?
7266 {
7267 correlation.push(format!("({})", sql));
7268 }
7269 }
7270 }
7271 if correlation.is_empty() {
7272 correlation.push("true".to_string());
7273 }
7274
7275 Ok(format!(
7276 "EXISTS (SELECT 1 FROM {} AS {} WHERE {})",
7277 source,
7278 postrust_sql::escape_ident(&alias),
7279 correlation.join(" AND ")
7280 ))
7281}
7282
7283fn table_exists_sql(
7308 spec: &serde_json::Value,
7309 scope: &WhereScope<'_>,
7310 param_idx: &mut usize,
7311 values: &mut Vec<serde_json::Value>,
7312 alias_counter: &mut usize,
7313) -> Result<String, async_graphql::Error> {
7314 let Some((schema, table)) = crate::role::exists_target(spec, &scope.qualified.schema) else {
7315 return Err(async_graphql::Error::new(
7316 "\"_exists\" needs a \"_table\" to look in",
7317 ));
7318 };
7319
7320 let qualified = postrust_core::api_request::QualifiedIdentifier::new(&schema, &table);
7324 if scope
7325 .resolution
7326 .as_ref()
7327 .is_some_and(|r| r.cache.get_table(&qualified).is_none())
7328 {
7329 return Err(async_graphql::Error::new(format!(
7330 "\"_exists\" names a table this server does not have: \"{}.{}\"",
7331 schema, table
7332 )));
7333 }
7334
7335 *alias_counter += 1;
7336 let alias = format!("pgrst_exists_{}", alias_counter);
7337 let type_name = scope
7341 .names
7342 .base_name(&schema, &table)
7343 .unwrap_or(&table)
7344 .to_string();
7345 let mut inner = WhereScope::aliased(&schema, &table, &alias, &type_name, scope);
7346 inner.caller = None;
7349
7350 let condition = match spec.get("_where") {
7351 Some(predicate) => build_condition(predicate, &inner, param_idx, values, alias_counter)?,
7352 None => None,
7353 };
7354
7355 Ok(format!(
7356 "EXISTS (SELECT 1 FROM {}.{} AS {} WHERE {})",
7357 postrust_sql::escape_ident(&schema),
7358 postrust_sql::escape_ident(&table),
7359 postrust_sql::escape_ident(&alias),
7360 condition.unwrap_or_else(|| "true".to_string())
7361 ))
7362}
7363
7364fn aggregate_predicate_sql(
7379 relationship: &RelationshipField,
7380 expression: &serde_json::Value,
7381 scope: &WhereScope<'_>,
7382 param_idx: &mut usize,
7383 values: &mut Vec<serde_json::Value>,
7384 alias_counter: &mut usize,
7385) -> Result<String, async_graphql::Error> {
7386 let serde_json::Value::Object(over) = expression else {
7387 return Ok("true".to_string());
7388 };
7389
7390 let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7391 async_graphql::Error::new(format!(
7392 "filtering on the relationship \"{}\" is not available here",
7393 relationship.name
7394 ))
7395 })?;
7396 let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7397 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7398 if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7399 return Err(async_graphql::Error::new(format!(
7400 "aggregating over \"{}\" is not supported: it is reached through a junction \
7401 rather than by a key",
7402 relationship.name
7403 )));
7404 }
7405
7406 let mut conditions: Vec<String> = Vec::new();
7407 for (function, spec) in over {
7408 if spec.is_null() {
7409 continue;
7410 }
7411 let serde_json::Value::Object(spec) = spec else {
7412 continue;
7413 };
7414 let Some(predicate) = spec.get("predicate").filter(|p| !p.is_null()) else {
7415 return Err(validation_error(format!(
7416 "\"{}\" over \"{}\" needs a predicate",
7417 function, relationship.name
7418 )));
7419 };
7420
7421 *alias_counter += 1;
7422 let alias = format!("pgrst_agg_{}", alias_counter);
7423 let child_scope = WhereScope::aliased(
7424 &plan.foreign_schema,
7425 &plan.foreign_table,
7426 &alias,
7427 &relationship.target_type,
7428 scope,
7429 );
7430
7431 let (source, mut correlation) = match &plan.function {
7434 Some(routine) => (
7435 format!(
7436 "{}.{}({})",
7437 postrust_sql::escape_ident(&routine.schema),
7438 postrust_sql::escape_ident(&routine.name),
7439 scope.row_ref
7440 ),
7441 Vec::new(),
7442 ),
7443 None => {
7444 let mut columns = Vec::with_capacity(plan.columns.len());
7445 for (parent_column, child_column) in &plan.columns {
7446 columns.push(format!(
7447 "{} = {}",
7448 scope.column(parent_column),
7449 child_scope.column(child_column)
7450 ));
7451 }
7452 (
7453 format!(
7454 "{}.{}",
7455 postrust_sql::escape_ident(&plan.foreign_schema),
7456 postrust_sql::escape_ident(&plan.foreign_table)
7457 ),
7458 columns,
7459 )
7460 }
7461 };
7462 if let Some(filter) = spec.get("filter").filter(|f| !f.is_null()) {
7465 if let Some(sql) =
7466 build_condition(filter, &child_scope, param_idx, values, alias_counter)?
7467 {
7468 correlation.push(format!("({})", sql));
7469 }
7470 }
7471 if correlation.is_empty() {
7472 correlation.push("true".to_string());
7473 }
7474
7475 let arguments = spec.get("arguments").filter(|a| !a.is_null());
7476 let distinct = matches!(spec.get("distinct"), Some(serde_json::Value::Bool(true)));
7477 let (aggregate, operand_type) = match function.as_str() {
7478 "count" => {
7479 let columns: Vec<String> = match arguments {
7480 Some(serde_json::Value::Array(items)) => items
7481 .iter()
7482 .filter_map(|item| item.as_str())
7483 .map(|column| child_scope.column(column))
7484 .collect(),
7485 Some(serde_json::Value::String(one)) => vec![child_scope.column(one)],
7486 _ => Vec::new(),
7487 };
7488 let counted = match columns.len() {
7489 0 => "*".to_string(),
7490 1 => columns.into_iter().next().unwrap_or_default(),
7491 _ => format!("({})", columns.join(", ")),
7492 };
7493 let counted = match distinct && counted != "*" {
7494 true => format!("DISTINCT {}", counted),
7495 false => counted,
7496 };
7497 (format!("count({})", counted), "integer")
7498 }
7499 "bool_and" | "bool_or" => {
7500 let Some(column) = arguments.and_then(|a| a.as_str()) else {
7501 return Err(validation_error(format!(
7502 "\"{}\" over \"{}\" needs the column to fold",
7503 function, relationship.name
7504 )));
7505 };
7506 (
7507 format!("{}({})", function, child_scope.column(column)),
7508 "boolean",
7509 )
7510 }
7511 other => {
7512 return Err(validation_error(format!(
7513 "\"{}\" is not an aggregate a predicate can be written over",
7514 other
7515 )))
7516 }
7517 };
7518
7519 let subselect = format!(
7520 "(SELECT {} FROM {} AS {} WHERE {})",
7521 aggregate,
7522 source,
7523 postrust_sql::escape_ident(&alias),
7524 correlation.join(" AND ")
7525 );
7526 let serde_json::Value::Object(operators) = predicate else {
7527 return Err(validation_error(format!(
7528 "the predicate on \"{}\" is not a comparison",
7529 function
7530 )));
7531 };
7532 for (operator, operand) in operators {
7533 conditions.push(comparison_sql(
7534 &subselect,
7535 function,
7536 Some(operand_type),
7537 operator,
7538 operand,
7539 param_idx,
7540 values,
7541 )?);
7542 }
7543 }
7544
7545 Ok(match conditions.is_empty() {
7546 true => "true".to_string(),
7547 false => format!("({})", conditions.join(" AND ")),
7548 })
7549}
7550
7551#[allow(clippy::too_many_arguments)]
7559fn postgis_sql(
7560 quoted: &str,
7561 column_type: Option<&str>,
7562 function: &str,
7563 op: &str,
7564 operand: &serde_json::Value,
7565 param_idx: &mut usize,
7566 values: &mut Vec<serde_json::Value>,
7567) -> Result<String, async_graphql::Error> {
7568 let is_geography = column_type == Some("geography");
7569 let mut shape = |value: &serde_json::Value, param_idx: &mut usize| -> String {
7570 let placeholder = format!("${}", param_idx);
7571 *param_idx += 1;
7572 values.push(value.clone());
7573 match is_geography {
7574 true => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
7575 false => format!("ST_GeomFromGeoJSON({})", placeholder),
7576 }
7577 };
7578
7579 match op {
7580 "_st_d_within" | "_st_3d_d_within" => {
7582 let serde_json::Value::Object(spec) = operand else {
7583 return Err(async_graphql::Error::new(format!(
7584 "\"{}\" takes {{distance, from}}",
7585 op
7586 )));
7587 };
7588 let Some(from) = spec.get("from") else {
7589 return Err(async_graphql::Error::new(format!(
7590 "\"{}\" needs a shape to measure from",
7591 op
7592 )));
7593 };
7594 let Some(distance) = spec.get("distance") else {
7595 return Err(async_graphql::Error::new(format!(
7596 "\"{}\" needs a distance",
7597 op
7598 )));
7599 };
7600 let from_sql = shape(from, param_idx);
7601 let distance_sql = format!("${}", param_idx);
7602 *param_idx += 1;
7603 values.push(distance.clone());
7604 let spheroid = match (is_geography, spec.get("use_spheroid")) {
7609 (true, Some(use_spheroid)) if !use_spheroid.is_null() => {
7610 let placeholder = format!("${}", param_idx);
7611 *param_idx += 1;
7612 values.push(use_spheroid.clone());
7613 format!(", {}::boolean", placeholder)
7614 }
7615 _ => String::new(),
7616 };
7617 Ok(format!(
7618 "{}({}, {}, {}::float8{})",
7619 function, quoted, from_sql, distance_sql, spheroid
7620 ))
7621 }
7622 "_st_intersects_rast" => {
7624 let placeholder = format!("${}", param_idx);
7625 *param_idx += 1;
7626 values.push(operand.clone());
7627 Ok(format!("{}({}, {}::raster)", function, quoted, placeholder))
7628 }
7629 "_st_intersects_geom_nband" | "_st_intersects_nband_geom" => {
7631 let serde_json::Value::Object(spec) = operand else {
7632 return Err(async_graphql::Error::new(format!(
7633 "\"{}\" takes an object",
7634 op
7635 )));
7636 };
7637 let Some(geometry) = spec.get("geommin") else {
7638 return Err(async_graphql::Error::new(format!(
7639 "\"{}\" needs a shape",
7640 op
7641 )));
7642 };
7643 let band = spec.get("nband").filter(|v| !v.is_null());
7644 let geometry_sql = shape(geometry, param_idx);
7645 Ok(match band {
7646 None => format!("{}({}, {})", function, quoted, geometry_sql),
7647 Some(band) => {
7648 let placeholder = format!("${}", param_idx);
7649 *param_idx += 1;
7650 values.push(band.clone());
7651 format!(
7655 "{}({}, {}::int, {})",
7656 function, quoted, placeholder, geometry_sql
7657 )
7658 }
7659 })
7660 }
7661 _ => {
7663 let other = shape(operand, param_idx);
7664 Ok(format!("{}({}, {})", function, quoted, other))
7665 }
7666 }
7667}
7668
7669fn sql_array(
7675 items: &[serde_json::Value],
7676 cast: &str,
7677 param_idx: &mut usize,
7678 values: &mut Vec<serde_json::Value>,
7679) -> String {
7680 if items.is_empty() {
7681 return format!("ARRAY[]::{}", cast);
7682 }
7683 let placeholders: Vec<String> = items
7684 .iter()
7685 .map(|item| {
7686 let placeholder = format!("${}", param_idx);
7687 *param_idx += 1;
7688 values.push(item.clone());
7689 placeholder
7690 })
7691 .collect();
7692 format!("ARRAY[{}]::{}", placeholders.join(", "), cast)
7693}
7694
7695#[allow(clippy::too_many_arguments)]
7697fn comparison_sql(
7698 quoted: &str,
7699 column: &str,
7700 column_type: Option<&str>,
7701 op: &str,
7702 operand: &serde_json::Value,
7703 param_idx: &mut usize,
7704 values: &mut Vec<serde_json::Value>,
7705) -> Result<String, async_graphql::Error> {
7706 if op == "_cast" {
7712 let serde_json::Value::Object(casts) = operand else {
7713 return Err(async_graphql::Error::new(
7714 "_cast takes an object naming the type to compare as",
7715 ));
7716 };
7717 let mut conditions = Vec::new();
7718 for (target, comparisons) in casts {
7719 let pg_type = match target.as_str() {
7723 "geometry" | "geography" => target.as_str(),
7724 "String" => "text",
7725 _ => {
7726 return Err(async_graphql::Error::new(format!(
7727 "cannot compare \"{}\" as \"{}\"",
7728 column, target
7729 )))
7730 }
7731 };
7732 let serde_json::Value::Object(ops) = comparisons else {
7733 continue;
7734 };
7735 let cast = format!("{}::{}", quoted, pg_type);
7736 for (nested_op, nested_operand) in ops {
7737 conditions.push(comparison_sql(
7738 &cast,
7739 column,
7740 Some(pg_type),
7741 nested_op,
7742 nested_operand,
7743 param_idx,
7744 values,
7745 )?);
7746 }
7747 }
7748 return Ok(match conditions.len() {
7749 0 => "true".to_string(),
7750 1 => conditions.pop().expect("just counted"),
7751 _ => format!("({})", conditions.join(" AND ")),
7752 });
7753 }
7754
7755 if let Some(function) = crate::input::bool_exp::postgis_function(op) {
7758 return postgis_sql(
7759 quoted,
7760 column_type,
7761 function,
7762 op,
7763 operand,
7764 param_idx,
7765 values,
7766 );
7767 }
7768
7769 if let Some((operator, cast)) = crate::input::bool_exp::ltree_operator(op) {
7773 if cast.ends_with("[]") {
7774 let items = operand.as_array().ok_or_else(|| {
7775 async_graphql::Error::new(format!(
7776 "the \"{}\" comparison on \"{}\" takes a list",
7777 op, column
7778 ))
7779 })?;
7780 return Ok(format!(
7781 "{} {} {}",
7782 quoted,
7783 operator,
7784 sql_array(items, cast, param_idx, values)
7785 ));
7786 }
7787 let placeholder = format!("${}", param_idx);
7788 *param_idx += 1;
7789 values.push(operand.clone());
7790 return Ok(format!("{} {} {}::{}", quoted, operator, placeholder, cast));
7791 }
7792
7793 if let Some(function) = match op {
7798 "_jsonb_path_exists" => Some("jsonb_path_exists"),
7799 "_jsonb_path_match" => Some("jsonb_path_match"),
7800 _ => None,
7801 } {
7802 let placeholder = format!("${}", param_idx);
7803 *param_idx += 1;
7804 values.push(operand.clone());
7805 return Ok(format!(
7806 "{}({}, {}::jsonpath)",
7807 function, quoted, placeholder
7808 ));
7809 }
7810
7811 let binary = match op {
7813 "_eq" => Some("="),
7814 "_neq" => Some("<>"),
7815 "_gt" => Some(">"),
7816 "_gte" => Some(">="),
7817 "_lt" => Some("<"),
7818 "_lte" => Some("<="),
7819 "_like" => Some("LIKE"),
7820 "_nlike" => Some("NOT LIKE"),
7821 "_ilike" => Some("ILIKE"),
7822 "_nilike" => Some("NOT ILIKE"),
7823 "_similar" => Some("SIMILAR TO"),
7824 "_nsimilar" => Some("NOT SIMILAR TO"),
7825 "_regex" => Some("~"),
7826 "_nregex" => Some("!~"),
7827 "_iregex" => Some("~*"),
7828 "_niregex" => Some("!~*"),
7829 "_contains" => Some("@>"),
7830 "_contained_in" => Some("<@"),
7831 "_has_key" => Some("?"),
7832 _ => None,
7833 };
7834
7835 if let Some(sql_op) = binary {
7836 let placeholder = format!("${}", param_idx);
7837 *param_idx += 1;
7838 values.push(match op {
7843 "_contains" | "_contained_in" => match operand {
7844 serde_json::Value::String(_)
7845 | serde_json::Value::Number(_)
7846 | serde_json::Value::Bool(_) => serde_json::Value::String(operand.to_string()),
7847 other => other.clone(),
7848 },
7849 _ => operand.clone(),
7850 });
7851 let cast = match op {
7863 "_contains" | "_contained_in" => "::jsonb".to_string(),
7864 "_has_key" => "::text".to_string(),
7865 _ if operand.is_null() => match column_type {
7874 Some(pg_type) => format!("::{}", pg_type),
7875 None => String::new(),
7876 },
7877 _ if operand.is_string() => match column_type {
7888 Some(pg_type) => format!("::{}", pg_type),
7889 None => String::new(),
7890 },
7891 _ => String::new(),
7892 };
7893 return Ok(format!("{} {} {}{}", quoted, sql_op, placeholder, cast));
7894 }
7895
7896 match op {
7897 "_is_null" => Ok(if operand.as_bool().unwrap_or(false) {
7898 format!("{} IS NULL", quoted)
7899 } else {
7900 format!("{} IS NOT NULL", quoted)
7901 }),
7902 "_in" | "_nin" => {
7903 let items = operand.as_array().ok_or_else(|| {
7904 async_graphql::Error::new(format!(
7905 "the \"{}\" comparison on \"{}\" requires a list of values",
7906 op, column
7907 ))
7908 })?;
7909 if items.is_empty() {
7910 return Ok(if op == "_in" { "false" } else { "true" }.to_string());
7913 }
7914 let mut placeholders = Vec::with_capacity(items.len());
7915 for item in items {
7916 let cast = match (item.is_null() || item.is_string(), column_type) {
7919 (true, Some(pg_type)) => format!("::{}", pg_type),
7920 _ => String::new(),
7921 };
7922 placeholders.push(format!("${}{}", param_idx, cast));
7923 values.push(item.clone());
7924 *param_idx += 1;
7925 }
7926 Ok(format!(
7927 "{} {} ({})",
7928 quoted,
7929 if op == "_in" { "IN" } else { "NOT IN" },
7930 placeholders.join(", ")
7931 ))
7932 }
7933 "_has_keys_any" | "_has_keys_all" => {
7934 let items = operand.as_array().ok_or_else(|| {
7935 async_graphql::Error::new(format!(
7936 "the \"{}\" comparison on \"{}\" requires a list of keys",
7937 op, column
7938 ))
7939 })?;
7940 Ok(format!(
7941 "{} {} {}",
7942 quoted,
7943 if op == "_has_keys_any" { "?|" } else { "?&" },
7944 sql_array(items, "text[]", param_idx, values)
7945 ))
7946 }
7947 other => Err(async_graphql::Error::new(format!(
7948 "unsupported comparison \"{}\" on \"{}\"",
7949 other, column
7950 ))),
7951 }
7952}
7953
7954#[allow(clippy::too_many_arguments)] async fn build_order_by_clause(
7964 ctx: &ResolverContext<'_>,
7965 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
7966 schema_name: &str,
7967 table_name: &str,
7968 type_name: &str,
7969 relationships: &HashMap<String, Vec<RelationshipField>>,
7970 reference: &str,
7971 names: &crate::names::NameOverrides,
7972) -> Result<String, async_graphql::Error> {
7973 let Ok(order_arg) = ctx.args.try_get("order_by") else {
7974 return Ok(String::new());
7975 };
7976
7977 let value = accessor_to_json(&order_arg);
7978 let entries: Vec<&serde_json::Value> = match &value {
7981 serde_json::Value::Array(items) => items.iter().collect(),
7982 serde_json::Value::Object(_) => vec![&value],
7983 serde_json::Value::Null => return Ok(String::new()),
7984 _ => {
7985 return Err(async_graphql::Error::new(
7986 "order_by takes an object or a list of them",
7987 ))
7988 }
7989 };
7990 if entries.is_empty() {
7991 return Ok(String::new());
7992 }
7993
7994 let guard = schema_cache
7995 .get()
7996 .await
7997 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7998 let cache = guard
7999 .as_ref()
8000 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8001 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8002 let table = cache
8003 .get_table(&qi)
8004 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8005
8006 let mut terms = Vec::new();
8007 let mut alias_counter = 0usize;
8008 for entry in entries {
8009 order_terms_into(
8010 entry,
8011 cache,
8012 relationships,
8013 type_name,
8014 table,
8015 reference,
8016 names,
8017 &mut alias_counter,
8018 &mut terms,
8019 )?;
8020 }
8021
8022 if terms.is_empty() {
8023 return Ok(String::new());
8024 }
8025 Ok(format!(" ORDER BY {}", terms.join(", ")))
8026}
8027
8028#[allow(clippy::too_many_arguments)]
8036fn order_terms_into(
8037 entry: &serde_json::Value,
8038 cache: &SchemaCache,
8039 relationships: &HashMap<String, Vec<RelationshipField>>,
8040 type_name: &str,
8041 table: &postrust_core::schema_cache::Table,
8042 reference: &str,
8043 names: &crate::names::NameOverrides,
8044 alias_counter: &mut usize,
8045 terms: &mut Vec<String>,
8046) -> Result<(), async_graphql::Error> {
8047 let serde_json::Value::Object(map) = entry else {
8048 return Err(async_graphql::Error::new(
8049 "each order_by entry is an object mapping a column to a direction",
8050 ));
8051 };
8052 let available: &[RelationshipField] = relationships
8053 .get(type_name)
8054 .map(|r| r.as_slice())
8055 .unwrap_or(&[]);
8056
8057 for (key, value) in map {
8058 if value.is_null() {
8064 continue;
8065 }
8066
8067 if let Some(name) = value.as_str() {
8069 let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8070 async_graphql::Error::new(format!(
8071 "\"{}\" is not a sort direction; expected one of asc, desc, \
8072 asc_nulls_first, asc_nulls_last, desc_nulls_first, desc_nulls_last",
8073 name
8074 ))
8075 })?;
8076 let column = table_column_for(names, table, key);
8077 if table.get_column(column).is_some() {
8078 terms.push(format!(
8079 "{}.{} {}",
8080 reference,
8081 postrust_sql::escape_ident(column),
8082 sql
8083 ));
8084 continue;
8085 }
8086 let function = names
8089 .computed_source(&table.schema, &table.name, key)
8090 .unwrap_or(key);
8091 if let Some(definition) = table.get_computed_column(function) {
8092 terms.push(format!(
8093 "{} {}",
8094 computed_call(definition, &format!("{}.*", reference), reference),
8095 sql
8096 ));
8097 continue;
8098 }
8099 return Err(async_graphql::Error::new(format!(
8100 "cannot order by unknown column \"{}\" on \"{}\"",
8101 key, table.name
8102 )));
8103 }
8104
8105 if let Some(rel) = key
8107 .strip_suffix("_aggregate")
8108 .and_then(|name| available.iter().find(|r| r.name == name && r.is_list))
8109 {
8110 aggregate_order_terms(value, rel, cache, reference, alias_counter, terms)?;
8111 continue;
8112 }
8113
8114 if let Some(rel) = available.iter().find(|r| r.name == *key && !r.is_list) {
8116 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8117 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8118 if plan.columns.is_empty() {
8119 return Err(async_graphql::Error::new(format!(
8120 "cannot order by \"{}\": it is not reached by a key",
8121 key
8122 )));
8123 }
8124 *alias_counter += 1;
8125 let alias = format!("pgrst_ord_{}", alias_counter);
8126 let quoted_alias = postrust_sql::escape_ident(&alias);
8127 let correlation = plan
8128 .columns
8129 .iter()
8130 .map(|(local, foreign)| {
8131 format!(
8132 "{} = {}.{}",
8133 format_args!("{}.{}", reference, postrust_sql::escape_ident(local)),
8134 quoted_alias,
8135 postrust_sql::escape_ident(foreign)
8136 )
8137 })
8138 .collect::<Vec<_>>()
8139 .join(" AND ");
8140
8141 let target_qi = postrust_core::api_request::QualifiedIdentifier::new(
8142 &plan.foreign_schema,
8143 &plan.foreign_table,
8144 );
8145 let Some(target) = cache.get_table(&target_qi) else {
8146 return Err(async_graphql::Error::new(format!(
8147 "unknown table \"{}\"",
8148 plan.foreign_table
8149 )));
8150 };
8151
8152 let mut nested = Vec::new();
8155 order_terms_into(
8156 value,
8157 cache,
8158 relationships,
8159 &rel.target_type,
8160 target,
8161 "ed_alias,
8162 names,
8163 alias_counter,
8164 &mut nested,
8165 )?;
8166 for term in nested {
8167 let (expression, direction) = term
8168 .rsplit_once(' ')
8169 .map(|(e, d)| (e.to_string(), d.to_string()))
8170 .unwrap_or((term.clone(), String::new()));
8171 terms.push(format!(
8172 "(SELECT {} FROM {}.{} AS {} WHERE {}) {}",
8173 expression,
8174 postrust_sql::escape_ident(&plan.foreign_schema),
8175 postrust_sql::escape_ident(&plan.foreign_table),
8176 quoted_alias,
8177 correlation,
8178 direction
8179 ));
8180 }
8181 continue;
8182 }
8183
8184 return Err(async_graphql::Error::new(format!(
8185 "cannot order by \"{}\" on \"{}\"",
8186 key, table.name
8187 )));
8188 }
8189 Ok(())
8190}
8191
8192fn aggregate_order_terms(
8194 value: &serde_json::Value,
8195 rel: &RelationshipField,
8196 cache: &SchemaCache,
8197 reference: &str,
8198 alias_counter: &mut usize,
8199 terms: &mut Vec<String>,
8200) -> Result<(), async_graphql::Error> {
8201 let serde_json::Value::Object(spec) = value else {
8202 return Err(async_graphql::Error::new(
8203 "ordering by an aggregate takes an object, such as {count: desc}",
8204 ));
8205 };
8206 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8207 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8208 if plan.columns.is_empty() && plan.function.is_none() {
8209 return Err(async_graphql::Error::new(format!(
8210 "cannot order by an aggregate of \"{}\": it is not reached by a key",
8211 rel.name
8212 )));
8213 }
8214 if plan.function.is_some() && !rel.arguments.is_empty() {
8217 return Err(async_graphql::Error::new(format!(
8218 "cannot order by an aggregate of \"{}\": it takes arguments, and an \
8219 ordering has nowhere to write them",
8220 rel.name
8221 )));
8222 }
8223
8224 *alias_counter += 1;
8225 let alias = postrust_sql::escape_ident(&format!("pgrst_ord_{}", alias_counter));
8226 let (source, correlation) = match &plan.function {
8229 Some(function) => {
8230 let session = plan.row_argument.as_ref().and_then(|row_argument| {
8231 computed_session_argument(cache, function, row_argument)
8232 .map(|session| (row_argument.clone(), session))
8233 });
8234 let call = match session {
8235 Some((row_argument, session)) => format!(
8236 "{} => {}, {} => {}",
8237 postrust_sql::escape_ident(&row_argument),
8238 reference,
8239 postrust_sql::escape_ident(&session),
8240 SESSION_ARGUMENT
8241 ),
8242 None => reference.to_string(),
8243 };
8244 (
8245 format!(
8246 "{}.{}({})",
8247 postrust_sql::escape_ident(&function.schema),
8248 postrust_sql::escape_ident(&function.name),
8249 call
8250 ),
8251 "true".to_string(),
8252 )
8253 }
8254 None => (
8255 format!(
8256 "{}.{}",
8257 postrust_sql::escape_ident(&plan.foreign_schema),
8258 postrust_sql::escape_ident(&plan.foreign_table)
8259 ),
8260 plan.columns
8261 .iter()
8262 .map(|(local, foreign)| {
8263 format!(
8264 "{}.{} = {}.{}",
8265 reference,
8266 postrust_sql::escape_ident(local),
8267 alias,
8268 postrust_sql::escape_ident(foreign)
8269 )
8270 })
8271 .collect::<Vec<_>>()
8272 .join(" AND "),
8273 ),
8274 };
8275
8276 let mut wanted: Vec<(String, String)> = Vec::new();
8278 for (function, argument) in spec {
8279 if argument.is_null() {
8282 continue;
8283 }
8284 if function == "count" {
8285 let name = argument.as_str().unwrap_or_default();
8286 let Some(direction) = crate::input::order_by::direction_sql(name) else {
8287 return Err(async_graphql::Error::new(format!(
8288 "\"{}\" is not a sort direction",
8289 name
8290 )));
8291 };
8292 wanted.push(("count(*)".to_string(), direction.to_string()));
8293 continue;
8294 }
8295 let serde_json::Value::Object(columns) = argument else {
8296 continue;
8297 };
8298 for (column, direction) in columns {
8299 if direction.is_null() {
8300 continue;
8301 }
8302 let name = direction.as_str().unwrap_or_default();
8303 let Some(direction) = crate::input::order_by::direction_sql(name) else {
8304 return Err(async_graphql::Error::new(format!(
8305 "\"{}\" is not a sort direction",
8306 name
8307 )));
8308 };
8309 wanted.push((
8312 format!(
8313 "{}({}.{})",
8314 function,
8315 alias,
8316 postrust_sql::escape_ident(column)
8317 ),
8318 direction.to_string(),
8319 ));
8320 }
8321 }
8322
8323 for (expression, direction) in wanted {
8324 terms.push(format!(
8325 "(SELECT {} FROM {} AS {} WHERE {}) {}",
8326 expression, source, alias, correlation, direction
8327 ));
8328 }
8329 Ok(())
8330}
8331
8332fn distinct_on_clause(
8346 distinct_on: &[String],
8347 written: Option<&str>,
8348) -> Result<(String, Option<String>), async_graphql::Error> {
8349 if distinct_on.is_empty() {
8350 return Ok((String::new(), written.map(str::to_string)));
8351 }
8352 let terms: Vec<&str> = written
8353 .map(|written| written.split(", ").collect())
8354 .unwrap_or_default();
8355 let column_of = |term: &str| {
8358 let expression = term.split_whitespace().next().unwrap_or(term);
8359 expression
8360 .rsplit_once('.')
8361 .map(|(_, name)| name)
8362 .unwrap_or(expression)
8363 .to_string()
8364 };
8365 let leading: Vec<String> = terms
8366 .iter()
8367 .take(distinct_on.len())
8368 .map(|term| column_of(term))
8369 .collect();
8370 if !terms.is_empty()
8371 && (leading.len() != distinct_on.len()
8372 || distinct_on
8373 .iter()
8374 .any(|column| !leading.contains(&column_of(column))))
8375 {
8376 return Err(validation_error(
8377 "\"distinct_on\" columns must match initial \"order_by\" columns",
8378 ));
8379 }
8380 let order = match terms.is_empty() {
8381 true => Some(distinct_on.join(", ")),
8382 false => written.map(str::to_string),
8383 };
8384 Ok((format!("DISTINCT ON ({}) ", distinct_on.join(", ")), order))
8385}
8386
8387async fn build_distinct_on(
8395 ctx: &ResolverContext<'_>,
8396 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8397 schema_name: &str,
8398 table_name: &str,
8399 names: &crate::names::NameOverrides,
8400) -> Result<Vec<String>, async_graphql::Error> {
8401 let Ok(arg) = ctx.args.try_get("distinct_on") else {
8402 return Ok(Vec::new());
8403 };
8404 let value = accessor_to_json(&arg);
8405 let requested: Vec<String> = match &value {
8406 serde_json::Value::Array(items) => items
8407 .iter()
8408 .filter_map(|i| i.as_str().map(|s| s.to_string()))
8409 .collect(),
8410 serde_json::Value::String(one) => vec![one.clone()],
8411 _ => Vec::new(),
8412 };
8413 if requested.is_empty() {
8414 return Ok(Vec::new());
8415 }
8416
8417 let guard = schema_cache
8418 .get()
8419 .await
8420 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8421 let cache = guard
8422 .as_ref()
8423 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8424 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8425 let table = cache
8426 .get_table(&qi)
8427 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8428
8429 let mut quoted = Vec::with_capacity(requested.len());
8430 for field in requested {
8431 let name = table_column_for(names, table, &field).to_string();
8432 if table.get_column(&name).is_none() {
8433 return Err(async_graphql::Error::new(format!(
8434 "cannot take distinct on unknown column \"{}\" of \"{}\"",
8435 field, table_name
8436 )));
8437 }
8438 quoted.push(postrust_sql::escape_ident(&name));
8439 }
8440 Ok(quoted)
8441}
8442
8443#[allow(clippy::too_many_arguments)] fn computed_projections<'a>(
8457 table: &postrust_core::schema_cache::Table,
8458 fields: impl IntoIterator<Item = SelectionField<'a>>,
8459 row_reference: &str,
8460 names: &crate::names::NameOverrides,
8461 schema_cache: &SchemaCache,
8462 param_idx: &mut usize,
8463 values: &mut Vec<serde_json::Value>,
8464) -> Result<Vec<String>, async_graphql::Error> {
8465 let mut projections = Vec::new();
8466 for field in fields {
8467 let name = field.name();
8468 if table
8471 .get_column(table_column_for(names, table, name))
8472 .is_some()
8473 {
8474 continue;
8475 }
8476 let function = names
8479 .computed_source(&table.schema, &table.name, name)
8480 .unwrap_or(name);
8481 let Some(computed) = table.get_computed_column(function) else {
8482 continue;
8483 };
8484 let arguments = crate::schema::computed_caller_arguments(computed, schema_cache);
8485 let given = match arguments.is_empty() {
8486 true => None,
8487 false => embed_arguments(field).remove("args"),
8488 };
8489 projections.push(format!(
8490 "{} AS {}",
8491 computed_column_call(
8492 computed,
8493 row_reference,
8494 name,
8495 &arguments,
8496 given.as_ref(),
8497 param_idx,
8498 values,
8499 )?,
8500 postrust_sql::escape_ident(name)
8501 ));
8502 }
8503 Ok(projections)
8504}
8505
8506fn computed_column_call(
8515 computed: &postrust_core::schema_cache::ComputedColumn,
8516 row: &str,
8517 field_name: &str,
8518 arguments: &[(String, String, bool)],
8519 given: Option<&serde_json::Value>,
8520 param_idx: &mut usize,
8521 values: &mut Vec<serde_json::Value>,
8522) -> Result<String, async_graphql::Error> {
8523 if arguments.is_empty() {
8524 return Ok(computed_call(computed, row, row));
8525 }
8526 let Some(row_argument) = &computed.row_argument else {
8527 return Ok(computed_call(computed, row, row));
8528 };
8529 let function = format!(
8530 "{}.{}",
8531 postrust_sql::escape_ident(&computed.function.schema),
8532 postrust_sql::escape_ident(&computed.function.name)
8533 );
8534 let mut passed = vec![format!(
8535 "{} => {}",
8536 postrust_sql::escape_ident(row_argument),
8537 row
8538 )];
8539 if let Some(session) = &computed.session_argument {
8540 passed.push(format!(
8541 "{} => {}",
8542 postrust_sql::escape_ident(session),
8543 SESSION_ARGUMENT
8544 ));
8545 }
8546 for (name, pg_type, required) in arguments {
8547 match given.and_then(|given| given.get(name)) {
8548 None if *required => {
8549 return Err(async_graphql::Error::new(format!(
8550 "{} needs the argument \"{}\"",
8551 field_name, name
8552 )))
8553 }
8554 None => continue,
8556 Some(value) => {
8557 passed.push(format!(
8558 "{} => ${}::{}",
8559 postrust_sql::escape_ident(name),
8560 param_idx,
8561 pg_type
8562 ));
8563 *param_idx += 1;
8564 values.push(value.clone());
8565 }
8566 }
8567 }
8568 Ok(format!("{}({})", function, passed.join(", ")))
8569}
8570
8571fn count_expression(
8583 field: async_graphql::SelectionField<'_>,
8584 table: (&str, &str),
8585 alias: Option<&str>,
8586 names: &crate::names::NameOverrides,
8587) -> String {
8588 let arguments = embed_arguments(field);
8589 let requested = match arguments.get("columns") {
8590 Some(serde_json::Value::Array(items)) => items
8591 .iter()
8592 .filter_map(|item| item.as_str())
8593 .collect::<Vec<_>>(),
8594 Some(serde_json::Value::String(one)) => vec![one.as_str()],
8595 _ => Vec::new(),
8596 };
8597 if requested.is_empty() {
8598 return "count(*)".to_string();
8599 }
8600 let columns: Vec<String> = requested
8601 .into_iter()
8602 .map(|column| {
8603 let (schema, name) = table;
8604 let source = column_for(names, schema, name, column);
8605 match alias {
8606 Some(alias) => format!(
8607 "{}.{}",
8608 postrust_sql::escape_ident(alias),
8609 postrust_sql::escape_ident(source)
8610 ),
8611 None => postrust_sql::escape_ident(source),
8612 }
8613 })
8614 .collect();
8615 let counted = match columns.len() {
8618 1 => columns.into_iter().next().unwrap_or_default(),
8619 _ => format!("({})", columns.join(", ")),
8620 };
8621 let distinct = matches!(
8622 arguments.get("distinct"),
8623 Some(serde_json::Value::Bool(true))
8624 );
8625 match distinct {
8626 true => format!("count(DISTINCT {})", counted),
8627 false => format!("count({})", counted),
8628 }
8629}
8630
8631#[allow(clippy::too_many_arguments)] fn nested_aggregate_select(
8639 caller: &crate::role::Caller<'_>,
8640 selection: async_graphql::SelectionField<'_>,
8641 child_alias: &str,
8642 schema_cache: &SchemaCache,
8643 relationships: &HashMap<String, Vec<RelationshipField>>,
8644 target_type: &str,
8645 child_table: Option<&postrust_core::schema_cache::Table>,
8646 max_rows: Option<i64>,
8647 alias_counter: &mut usize,
8648 param_idx: &mut usize,
8649 values: &mut Vec<serde_json::Value>,
8650 names: &crate::names::NameOverrides,
8651) -> Result<String, async_graphql::Error> {
8652 use crate::schema::aggregate as agg;
8653
8654 let mut parts: Vec<String> = Vec::new();
8655
8656 for child in selection.selection_set() {
8657 match child.name() {
8658 "aggregate" => {
8659 let of_table = child_table
8660 .map(|table| (table.schema.as_str(), table.name.as_str()))
8661 .unwrap_or(("", ""));
8662 let mut counts: Vec<(String, String)> = Vec::new();
8667 let mut wanted: BTreeMap<&str, Vec<String>> = BTreeMap::new();
8668 for function in child.selection_set() {
8669 if function.name() == "count" {
8670 counts.push((
8671 function.alias().unwrap_or("count").to_string(),
8672 count_expression(function, of_table, Some(child_alias), names),
8673 ));
8674 continue;
8675 }
8676 let sql_function = agg::NUMERIC_AGGREGATES
8677 .iter()
8678 .chain(agg::ORDERED_AGGREGATES.iter())
8679 .find(|(name, _)| *name == function.name())
8680 .map(|(name, _)| *name);
8681 let Some(sql_function) = sql_function else {
8682 continue;
8683 };
8684 let columns = wanted.entry(sql_function).or_default();
8685 for column in function.selection_set() {
8686 let name = column.name().to_string();
8687 if !columns.contains(&name) {
8688 columns.push(name);
8689 }
8690 }
8691 }
8692 let mut fields = match counts.iter().any(|(key, _)| key == "count") {
8695 true => Vec::new(),
8696 false => vec!["'count', count(*)".to_string()],
8697 };
8698 for (key, expression) in &counts {
8699 fields.push(format!("'{}', {}", key.replace('\'', "''"), expression));
8700 }
8701 for (sql_function, named) in &wanted {
8702 let columns: Vec<String> = named
8703 .iter()
8704 .map(|column| {
8705 let source = child_table
8706 .map(|table| table_column_for(names, table, column))
8707 .unwrap_or(column.as_str());
8708 format!(
8709 "'{}', {}({}.{})",
8710 column.replace('\'', "''"),
8711 sql_function,
8712 postrust_sql::escape_ident(child_alias),
8713 postrust_sql::escape_ident(source)
8714 )
8715 })
8716 .collect();
8717 if !columns.is_empty() {
8718 fields.push(format!(
8719 "'{}', json_build_object({})",
8720 sql_function,
8721 columns.join(", ")
8722 ));
8723 }
8724 }
8725 parts.push(format!(
8726 "json_build_object({}) AS {}",
8727 fields.join(", "),
8728 postrust_sql::escape_ident("aggregate")
8729 ));
8730 }
8731 "nodes" => {
8736 let embeds = build_embed_expressions(
8737 caller,
8738 schema_cache,
8739 relationships,
8740 target_type,
8741 child_alias,
8742 child.selection_set(),
8743 max_rows,
8744 alias_counter,
8745 param_idx,
8746 values,
8747 names,
8748 )?;
8749 let mut columns: Vec<String> = Vec::new();
8750 for column in child.selection_set() {
8751 let name = column.name();
8752 if name == "__typename" || embeds.iter().any(|(field, _)| field == name) {
8753 continue;
8754 }
8755 columns.push(format!(
8756 "'{}', {}",
8757 name.replace('\'', "''"),
8758 column_expression(child_table, child_alias, name, names)
8759 ));
8760 }
8761 for (field, expression) in &embeds {
8762 columns.push(format!("'{}', {}", field.replace('\'', "''"), expression));
8763 }
8764 let row = if columns.is_empty() {
8765 format!("row_to_json({})", postrust_sql::escape_ident(child_alias))
8766 } else {
8767 format!("json_build_object({})", columns.join(", "))
8768 };
8769 parts.push(format!(
8770 "COALESCE(json_agg({}), '[]'::json) AS {}",
8771 row,
8772 postrust_sql::escape_ident("nodes")
8773 ));
8774 }
8775 _ => {}
8776 }
8777 }
8778
8779 if parts.is_empty() {
8780 parts.push("count(*) AS pgrst_empty".to_string());
8783 }
8784 Ok(parts.join(", "))
8785}
8786
8787fn column_expression(
8792 table: Option<&postrust_core::schema_cache::Table>,
8793 alias: &str,
8794 name: &str,
8795 names: &crate::names::NameOverrides,
8796) -> String {
8797 let column = table
8798 .map(|t| table_column_for(names, t, name))
8799 .unwrap_or(name);
8800 let plain = format!(
8801 "{}.{}",
8802 postrust_sql::escape_ident(alias),
8803 postrust_sql::escape_ident(column)
8804 );
8805 let computed = table
8806 .filter(|t| t.get_column(column).is_none())
8807 .and_then(|t| {
8808 let function = names
8809 .computed_source(&t.schema, &t.name, name)
8810 .unwrap_or(name);
8811 t.get_computed_column(function)
8812 });
8813 match computed {
8814 Some(definition) => computed_call(
8815 definition,
8816 &format!("{}.*", postrust_sql::escape_ident(alias)),
8817 &postrust_sql::escape_ident(alias),
8818 ),
8819 None => plain,
8820 }
8821}
8822
8823fn order_terms(
8831 order: &serde_json::Value,
8832 schema_cache: &SchemaCache,
8833 schema_name: &str,
8834 table_name: &str,
8835 alias: &str,
8836 names: &crate::names::NameOverrides,
8837) -> Result<Option<String>, async_graphql::Error> {
8838 let entries: Vec<&serde_json::Value> = match order {
8839 serde_json::Value::Array(items) => items.iter().collect(),
8840 serde_json::Value::Object(_) => vec![order],
8841 _ => return Ok(None),
8842 };
8843
8844 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8845 let table = schema_cache
8846 .get_table(&qi)
8847 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8848
8849 let mut terms = Vec::new();
8850 for entry in entries {
8851 let serde_json::Value::Object(map) = entry else {
8852 continue;
8853 };
8854 for (field, direction) in map {
8855 if direction.is_null() {
8857 continue;
8858 }
8859 let column = table_column_for(names, table, field);
8860 if table.get_column(column).is_none() {
8861 return Err(async_graphql::Error::new(format!(
8862 "cannot order by unknown column \"{}\" on \"{}\"",
8863 field, table_name
8864 )));
8865 }
8866 let name = direction.as_str().unwrap_or_default();
8867 let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8868 async_graphql::Error::new(format!("\"{}\" is not a sort direction", name))
8869 })?;
8870 terms.push(format!(
8871 "{}.{} {}",
8872 postrust_sql::escape_ident(alias),
8873 postrust_sql::escape_ident(column),
8874 sql
8875 ));
8876 }
8877 }
8878
8879 Ok(if terms.is_empty() {
8880 None
8881 } else {
8882 Some(terms.join(", "))
8883 })
8884}
8885
8886fn always_present(rel: &RelationshipField, table: &postrust_core::schema_cache::Table) -> bool {
8898 use postrust_core::schema_cache::{Cardinality, Relationship};
8899 let Relationship::ForeignKey { cardinality, .. } = &rel.relationship else {
8900 return false;
8901 };
8902 match cardinality {
8903 Cardinality::M2O { .. } => {}
8904 Cardinality::O2O {
8905 is_parent: false, ..
8906 } => {}
8907 _ => return false,
8908 }
8909 let columns = cardinality.columns();
8910 !columns.is_empty()
8911 && columns.iter().all(|(local, _)| {
8912 table
8913 .get_column(local)
8914 .is_some_and(|column| !column.nullable)
8915 })
8916}
8917
8918fn child_holds_the_key(relationship: &postrust_core::schema_cache::Relationship) -> bool {
8926 use postrust_core::schema_cache::{Cardinality, Relationship};
8927 match relationship {
8928 Relationship::ForeignKey { cardinality, .. } => match cardinality {
8929 Cardinality::M2O { .. } => false,
8930 Cardinality::O2O { is_parent, .. } => *is_parent,
8931 _ => true,
8932 },
8933 Relationship::Computed { .. } => true,
8934 }
8935}
8936
8937fn computed_session_argument(
8939 cache: &SchemaCache,
8940 function: &postrust_core::api_request::QualifiedIdentifier,
8941 row_argument: &str,
8942) -> Option<String> {
8943 let _ = row_argument;
8944 cache
8945 .routines
8946 .get(function)
8947 .into_iter()
8948 .flatten()
8949 .find(|routine| routine.name == function.name)?
8950 .params
8951 .iter()
8952 .find(|param| {
8953 param.name == "hasura_session" && matches!(param.param_type.as_str(), "json" | "jsonb")
8954 })
8955 .map(|param| param.name.clone())
8956}
8957
8958fn computed_args_type_name(type_name: &str, field: &str) -> String {
8960 format!("{}_{}_args", type_name, field)
8961}
8962
8963fn computed_arguments(
8970 relationship: &RelationshipField,
8971 plan: &postrust_core::embed::EmbedPlan,
8972 parent_row: &str,
8973 given: Option<&serde_json::Value>,
8974 param_idx: &mut usize,
8975 values: &mut Vec<serde_json::Value>,
8976 schema_cache: &SchemaCache,
8977) -> Result<Option<String>, async_graphql::Error> {
8978 let (Some(function), Some(row_argument)) = (&plan.function, &plan.row_argument) else {
8979 return Ok(None);
8980 };
8981 let session = schema_cache
8982 .routines
8983 .get(function)
8984 .into_iter()
8985 .flatten()
8986 .find(|routine| routine.name == function.name)
8987 .and_then(|routine| {
8988 routine
8989 .params
8990 .iter()
8991 .find(|param| {
8992 param.name == "hasura_session"
8993 && matches!(param.param_type.as_str(), "json" | "jsonb")
8994 })
8995 .map(|param| param.name.clone())
8996 });
8997 if relationship.arguments.is_empty() && session.is_none() {
8998 return Ok(None);
8999 }
9000
9001 let mut passed = vec![format!(
9002 "{} => {}",
9003 postrust_sql::escape_ident(row_argument),
9004 parent_row
9005 )];
9006 if let Some(session) = session {
9007 passed.push(format!(
9008 "{} => {}",
9009 postrust_sql::escape_ident(&session),
9010 SESSION_ARGUMENT
9011 ));
9012 }
9013 for (name, pg_type, required) in &relationship.arguments {
9014 match given.and_then(|given| given.get(name)) {
9015 None if *required => {
9016 return Err(async_graphql::Error::new(format!(
9017 "{} needs the argument \"{}\"",
9018 relationship.name, name
9019 )))
9020 }
9021 None => continue,
9023 Some(value) => {
9024 passed.push(format!(
9025 "{} => ${}::{}",
9026 postrust_sql::escape_ident(name),
9027 param_idx,
9028 pg_type
9029 ));
9030 *param_idx += 1;
9031 values.push(value.clone());
9032 }
9033 }
9034 }
9035 Ok(Some(passed.join(", ")))
9036}
9037
9038fn embed_arguments(field: async_graphql::SelectionField<'_>) -> HashMap<String, serde_json::Value> {
9040 field
9041 .arguments()
9042 .map(|args| {
9043 args.into_iter()
9044 .map(|(name, value)| {
9045 (
9046 name.to_string(),
9047 plain_numbers(value.into_json().unwrap_or(serde_json::Value::Null)),
9048 )
9049 })
9050 .collect()
9051 })
9052 .unwrap_or_default()
9053}
9054
9055type Narrowing = (String, Option<String>, Option<String>, Option<i64>, i64);
9059
9060#[allow(clippy::too_many_arguments)] fn embed_narrowing(
9067 arguments: &HashMap<String, serde_json::Value>,
9068 plan: &postrust_core::embed::EmbedPlan,
9069 schema_cache: &SchemaCache,
9070 relationships: &HashMap<String, Vec<RelationshipField>>,
9071 target_type: &str,
9072 child_alias: &str,
9073 max_rows: Option<i64>,
9074 param_idx: &mut usize,
9075 values: &mut Vec<serde_json::Value>,
9076 names: &crate::names::NameOverrides,
9077 caller: &crate::role::Caller<'_>,
9078) -> Result<Narrowing, async_graphql::Error> {
9079 let permission =
9083 permission_predicate(caller, names, &plan.foreign_schema, &plan.foreign_table)?;
9084 let requested = arguments.get("where").filter(|v| !v.is_null()).cloned();
9085 let child_where = match and_predicates(requested, permission) {
9086 Some(expression) => {
9087 let child_scope = WhereScope::for_alias(
9088 &plan.foreign_schema,
9089 &plan.foreign_table,
9090 child_alias,
9091 target_type,
9092 schema_cache,
9093 relationships,
9094 names,
9095 )
9096 .for_caller(*caller);
9097 let mut nested_alias = 0usize;
9098 build_condition(
9099 &expression,
9100 &child_scope,
9101 param_idx,
9102 values,
9103 &mut nested_alias,
9104 )?
9105 }
9106 None => None,
9107 };
9108
9109 let child_order = match arguments.get("order_by") {
9110 Some(order) if !order.is_null() => order_terms(
9111 order,
9112 schema_cache,
9113 &plan.foreign_schema,
9114 &plan.foreign_table,
9115 child_alias,
9116 names,
9117 )?,
9118 _ => None,
9119 };
9120
9121 let ceiling = match (
9126 max_rows,
9127 crate::role::read_limit(caller, names, &plan.foreign_schema, &plan.foreign_table),
9128 ) {
9129 (Some(configured), Some(granted)) => Some(configured.min(granted)),
9130 (only, None) | (None, only) => only,
9131 };
9132 let child_limit = match arguments.get("limit").and_then(|v| v.as_i64()) {
9133 Some(requested) => match ceiling {
9134 Some(ceiling) => Some(requested.min(ceiling)),
9135 None => Some(requested),
9136 },
9137 None => ceiling,
9138 };
9139 let child_offset = arguments
9140 .get("offset")
9141 .and_then(|v| v.as_i64())
9142 .unwrap_or(0);
9143
9144 let requested = match arguments.get("distinct_on") {
9148 Some(serde_json::Value::Array(items)) => items
9149 .iter()
9150 .filter_map(|item| item.as_str())
9151 .collect::<Vec<_>>(),
9152 Some(serde_json::Value::String(one)) => vec![one.as_str()],
9153 _ => Vec::new(),
9154 };
9155 let mut distinct_on = Vec::with_capacity(requested.len());
9156 if !requested.is_empty() {
9157 let qi = postrust_core::api_request::QualifiedIdentifier::new(
9158 &plan.foreign_schema,
9159 &plan.foreign_table,
9160 );
9161 let table = schema_cache.get_table(&qi).ok_or_else(|| {
9162 async_graphql::Error::new(format!("unknown table \"{}\"", plan.foreign_table))
9163 })?;
9164 for field in requested {
9165 let column = table_column_for(names, table, field).to_string();
9166 if table.get_column(&column).is_none() {
9167 return Err(async_graphql::Error::new(format!(
9168 "cannot take distinct on unknown column \"{}\" of \"{}\"",
9169 field, plan.foreign_table
9170 )));
9171 }
9172 distinct_on.push(format!(
9173 "{}.{}",
9174 postrust_sql::escape_ident(child_alias),
9175 postrust_sql::escape_ident(&column)
9176 ));
9177 }
9178 }
9179 let (distinct, child_order) = distinct_on_clause(&distinct_on, child_order.as_deref())?;
9180
9181 Ok((
9182 distinct,
9183 child_where,
9184 child_order,
9185 child_limit,
9186 child_offset,
9187 ))
9188}
9189
9190#[allow(clippy::too_many_arguments)] fn build_embed_expressions<'a>(
9192 caller: &crate::role::Caller<'_>,
9193 schema_cache: &SchemaCache,
9194 relationships: &HashMap<String, Vec<RelationshipField>>,
9195 type_name: &str,
9196 parent_alias: &str,
9197 fields: impl IntoIterator<Item = SelectionField<'a>>,
9198 max_rows: Option<i64>,
9199 alias_counter: &mut usize,
9200 param_idx: &mut usize,
9201 values: &mut Vec<serde_json::Value>,
9202 names: &crate::names::NameOverrides,
9203) -> Result<Vec<(String, String)>, async_graphql::Error> {
9204 let Some(available) = relationships.get(type_name) else {
9205 return Ok(Vec::new());
9206 };
9207
9208 let mut expressions = Vec::new();
9209
9210 for field in fields {
9211 let aggregate_of = field
9215 .name()
9216 .strip_suffix("_aggregate")
9217 .and_then(|name| available.iter().find(|r| r.name == name && r.is_list));
9218
9219 if let Some(rel) = aggregate_of {
9220 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9221 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9222 *alias_counter += 1;
9223 let child_alias = format!("a{}", alias_counter);
9224
9225 let arguments = embed_arguments(field);
9229 let (child_distinct, child_where, child_order, child_limit, child_offset) =
9230 embed_narrowing(
9231 &arguments,
9232 &plan,
9233 schema_cache,
9234 relationships,
9235 &rel.target_type,
9236 &child_alias,
9237 max_rows,
9238 param_idx,
9239 values,
9240 names,
9241 caller,
9242 )?;
9243
9244 let child_table =
9245 schema_cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
9246 &plan.foreign_schema,
9247 &plan.foreign_table,
9248 ));
9249 let select = nested_aggregate_select(
9250 caller,
9251 field,
9252 &child_alias,
9253 schema_cache,
9254 relationships,
9255 &rel.target_type,
9256 child_table,
9257 max_rows,
9258 alias_counter,
9259 param_idx,
9260 values,
9261 names,
9262 )?;
9263
9264 let call = computed_arguments(
9265 rel,
9266 &plan,
9267 &postrust_sql::escape_ident(parent_alias),
9268 arguments.get("args"),
9269 param_idx,
9270 values,
9271 schema_cache,
9272 )?;
9273 let expression = plan
9274 .aggregate_expression(
9275 parent_alias,
9276 &postrust_sql::escape_ident(parent_alias),
9277 &child_alias,
9278 &select,
9279 child_limit,
9280 child_offset,
9281 child_where.as_deref(),
9282 child_order.as_deref(),
9283 call.as_deref(),
9284 &child_distinct,
9285 postrust_core::EmbedRendering::Verbatim,
9289 )
9290 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9291 expressions.push((field.name().to_string(), expression));
9292 continue;
9293 }
9294
9295 let Some(rel) = available.iter().find(|r| r.name == field.name()) else {
9296 continue;
9297 };
9298
9299 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9300 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9301
9302 *alias_counter += 1;
9303 let child_alias = format!("e{}", alias_counter);
9304
9305 let nested = build_embed_expressions(
9306 caller,
9307 schema_cache,
9308 relationships,
9309 &rel.target_type,
9310 &child_alias,
9311 field.selection_set(),
9312 max_rows,
9313 alias_counter,
9314 param_idx,
9315 values,
9316 names,
9317 )?;
9318
9319 let arguments = embed_arguments(field);
9321 let (child_distinct, child_where, child_order, child_limit, child_offset) =
9322 embed_narrowing(
9323 &arguments,
9324 &plan,
9325 schema_cache,
9326 relationships,
9327 &rel.target_type,
9328 &child_alias,
9329 max_rows,
9330 param_idx,
9331 values,
9332 names,
9333 caller,
9334 )?;
9335
9336 let child_relationships = relationships.get(&rel.target_type);
9339 let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
9340 &plan.foreign_schema,
9341 &plan.foreign_table,
9342 );
9343 let child_table = schema_cache.get_table(&child_qi);
9344 let mut parts: Vec<String> = Vec::new();
9345 for sub in field.selection_set() {
9346 let name = sub.name();
9347 let is_relationship = child_relationships
9348 .map(|rels| {
9349 rels.iter().any(|r| {
9350 r.name == name || (r.is_list && format!("{}_aggregate", r.name) == name)
9351 })
9352 })
9353 .unwrap_or(false);
9354 if is_relationship {
9355 continue;
9356 }
9357 let column = child_table
9360 .map(|t| table_column_for(names, t, name))
9361 .unwrap_or(name);
9362 let computed = child_table
9363 .filter(|t| t.get_column(column).is_none())
9364 .and_then(|t| {
9365 let function = names
9366 .computed_source(&t.schema, &t.name, name)
9367 .unwrap_or(name);
9368 t.get_computed_column(function)
9369 });
9370 match computed {
9371 Some(definition) => parts.push(format!(
9372 "{} AS {}",
9373 computed_call(
9374 definition,
9375 &format!("{}.*", postrust_sql::escape_ident(&child_alias)),
9376 &postrust_sql::escape_ident(&child_alias),
9377 ),
9378 postrust_sql::escape_ident(name)
9379 )),
9380 None if column != name => parts.push(format!(
9383 "{} AS {}",
9384 postrust_sql::escape_ident(column),
9385 postrust_sql::escape_ident(name)
9386 )),
9387 None => parts.push(postrust_sql::escape_ident(name)),
9388 }
9389 }
9390 if parts.is_empty() && nested.is_empty() {
9391 parts.push(
9392 child_table
9393 .and_then(|table| rename_projection(table, &child_alias, names))
9394 .unwrap_or_else(|| format!("{}.*", postrust_sql::escape_ident(&child_alias))),
9395 );
9396 }
9397 for (field_name, expression) in nested {
9398 parts.push(format!(
9399 "{} AS {}",
9400 expression,
9401 postrust_sql::escape_ident(&field_name)
9402 ));
9403 }
9404
9405 let computed_call_arguments = computed_arguments(
9406 rel,
9407 &plan,
9408 &postrust_sql::escape_ident(parent_alias),
9409 arguments.get("args"),
9410 param_idx,
9411 values,
9412 schema_cache,
9413 )?;
9414 let expression = plan
9415 .embed_expression(
9416 parent_alias,
9417 &postrust_sql::escape_ident(parent_alias),
9421 &child_alias,
9422 &format!("{}{}", child_distinct, parts.join(", ")),
9423 child_limit,
9424 child_offset,
9425 child_where.as_deref(),
9426 child_order.as_deref(),
9427 computed_call_arguments.as_deref(),
9428 postrust_core::EmbedRendering::Verbatim,
9429 )
9430 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9431
9432 expressions.push((rel.name.clone(), expression));
9433 }
9434
9435 Ok(expressions)
9436}
9437
9438struct EmbedContext<'c> {
9444 schema_cache: &'c SchemaCache,
9445 relationships: &'c HashMap<String, Vec<RelationshipField>>,
9446 max_rows: Option<i64>,
9447}
9448
9449fn embed_relationships<'f>(
9455 conn: &'f mut sqlx::PgConnection,
9456 ctx: &'f EmbedContext<'f>,
9457 type_name: &'f str,
9458 selection: async_graphql::SelectionField<'f>,
9459 rows: &'f mut [serde_json::Value],
9460) -> futures::future::BoxFuture<'f, Result<(), async_graphql::Error>> {
9461 Box::pin(async move {
9462 if rows.is_empty() {
9463 return Ok(());
9464 }
9465
9466 let Some(available) = ctx.relationships.get(type_name) else {
9467 return Ok(());
9468 };
9469
9470 for requested in selection.selection_set() {
9471 let Some(rel) = available.iter().find(|r| r.name == requested.name()) else {
9472 continue;
9473 };
9474
9475 let plan =
9476 postrust_core::embed::EmbedPlan::resolve(&rel.relationship, ctx.schema_cache)
9477 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9478
9479 let keys = postrust_core::embed::parent_keys(rows, &plan.local_column);
9480
9481 let mut child_columns: Vec<String> = Vec::new();
9491 let mut project_everything = false;
9492 let child_relationships = ctx.relationships.get(&rel.target_type);
9493 for field in requested.selection_set() {
9494 let name = field.name();
9495 match child_relationships.and_then(|rels| rels.iter().find(|r| r.name == name)) {
9496 Some(nested_rel) => {
9497 match postrust_core::embed::EmbedPlan::resolve(
9498 &nested_rel.relationship,
9499 ctx.schema_cache,
9500 ) {
9501 Ok(nested_plan) => child_columns.push(nested_plan.local_column),
9502 Err(_) => project_everything = true,
9503 }
9504 }
9505 None => child_columns.push(name.to_string()),
9506 }
9507 }
9508 if project_everything || child_columns.is_empty() {
9509 child_columns.clear();
9510 }
9511
9512 let mut grouped = if keys.is_empty() {
9513 std::collections::HashMap::new()
9514 } else {
9515 let sql = plan
9516 .children_grouped_sql(ctx.max_rows, &child_columns)
9517 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9518
9519 let fetched = sqlx::query(&sql)
9520 .bind(&keys)
9521 .fetch_all(&mut *conn)
9522 .await
9523 .map_err(database_error)?;
9524
9525 use sqlx::Row;
9528 let pairs: Vec<(serde_json::Value, serde_json::Value)> = fetched
9529 .into_iter()
9530 .filter_map(|row| {
9531 Some((
9532 row.try_get::<serde_json::Value, _>(0).ok()?,
9533 row.try_get::<serde_json::Value, _>(1).ok()?,
9534 ))
9535 })
9536 .collect();
9537
9538 postrust_core::embed::group_from_aggregated(pairs)
9539 };
9540
9541 let has_deeper_embed = ctx
9546 .relationships
9547 .get(&rel.target_type)
9548 .map(|rels| {
9549 requested
9550 .selection_set()
9551 .any(|field| rels.iter().any(|r| r.name == field.name()))
9552 })
9553 .unwrap_or(false);
9554
9555 if has_deeper_embed {
9556 let mut order: Vec<(String, usize)> = Vec::with_capacity(grouped.len());
9557 let mut flat: Vec<serde_json::Value> = Vec::new();
9558 for (key, children) in grouped.drain() {
9559 order.push((key, children.len()));
9560 flat.extend(children);
9561 }
9562
9563 embed_relationships(&mut *conn, ctx, &rel.target_type, requested, &mut flat)
9564 .await?;
9565
9566 let mut rest = flat.into_iter();
9567 for (key, count) in order {
9568 grouped.insert(key, rest.by_ref().take(count).collect());
9569 }
9570 }
9571
9572 for row in rows.iter_mut() {
9573 postrust_core::embed::attach_to_parent(row, &rel.name, &plan, &grouped);
9574 }
9575 }
9576
9577 Ok(())
9578 })
9579}
9580
9581fn pk_where_from_args(
9586 ctx: &ResolverContext<'_>,
9587 schema_name: &str,
9588 table_name: &str,
9589 pk_columns: &[(String, String)],
9590 names: &crate::names::NameOverrides,
9591) -> Result<serde_json::Value, async_graphql::Error> {
9592 if pk_columns.is_empty() {
9593 return Err(async_graphql::Error::new(format!(
9594 "\"{}\" has no primary key, so it cannot be mutated by key",
9595 table_name
9596 )));
9597 }
9598
9599 let from_object = ctx
9603 .args
9604 .try_get("pk_columns")
9605 .ok()
9606 .map(|v| accessor_to_json(&v));
9607
9608 let mut conditions = serde_json::Map::new();
9609 for (col_name, _) in pk_columns {
9610 let field = names
9614 .column(schema_name, table_name, col_name)
9615 .unwrap_or(col_name);
9616 let value = match &from_object {
9617 Some(serde_json::Value::Object(map)) => map.get(field).cloned().ok_or_else(|| {
9618 async_graphql::Error::new(format!(
9619 "pk_columns is missing the key column \"{}\"",
9620 field
9621 ))
9622 })?,
9623 _ => {
9624 let arg = ctx.args.try_get(field).map_err(|_| {
9625 async_graphql::Error::new(format!(
9626 "missing required primary key argument \"{}\"",
9627 field
9628 ))
9629 })?;
9630 accessor_to_json(&arg)
9631 }
9632 };
9633 conditions.insert(field.to_string(), serde_json::json!({ "_eq": value }));
9634 }
9635
9636 Ok(serde_json::Value::Object(conditions))
9637}
9638
9639fn pk_argument_type(pg_type: &str) -> String {
9646 let rendered = crate::types::pg_type_to_graphql(pg_type).to_string();
9647 if rendered.starts_with('[') {
9648 "String".to_string()
9649 } else {
9650 rendered
9651 }
9652}
9653
9654fn exposed_pk_argument_type(
9659 names: &crate::names::NameOverrides,
9660 schema: &str,
9661 table: &str,
9662 column: &str,
9663 pg_type: &str,
9664) -> String {
9665 names
9666 .column_type(schema, table, column)
9667 .map(|given| given.to_string())
9668 .unwrap_or_else(|| pk_argument_type(pg_type))
9669}
9670
9671fn graphql_type_ref(type_str: &str) -> TypeRef {
9673 let is_list = type_str.starts_with('[');
9675 let is_nn = type_str.ends_with('!');
9676
9677 let inner = if is_list {
9679 let stripped = type_str
9680 .trim_end_matches('!') .trim_start_matches('[') .trim_end_matches(']'); stripped
9684 } else {
9685 type_str.trim_end_matches('!')
9686 };
9687
9688 let inner_nn = inner.ends_with('!');
9689 let base_type = inner.trim_end_matches('!');
9690
9691 if is_list {
9692 if is_nn {
9693 if inner_nn {
9694 TypeRef::named_nn_list_nn(base_type)
9695 } else {
9696 TypeRef::named_list_nn(base_type)
9697 }
9698 } else if inner_nn {
9699 TypeRef::named_nn_list(base_type)
9700 } else {
9701 TypeRef::named_list(base_type)
9702 }
9703 } else if is_nn {
9704 TypeRef::named_nn(base_type)
9705 } else {
9706 TypeRef::named(base_type)
9707 }
9708}
9709
9710const NUMBER_AS_TEXT: &str = "$serde_json::private::Number";
9720
9721fn number_kept_as_text(
9724 map: &serde_json::Map<String, serde_json::Value>,
9725) -> Option<serde_json::Value> {
9726 if map.len() != 1 {
9727 return None;
9728 }
9729 let text = map.get(NUMBER_AS_TEXT)?.as_str()?;
9730 serde_json::from_str::<serde_json::Value>(text)
9731 .ok()
9732 .filter(|value| value.is_number())
9733}
9734
9735fn plain_numbers(value: serde_json::Value) -> serde_json::Value {
9737 match value {
9738 serde_json::Value::Object(map) => match number_kept_as_text(&map) {
9739 Some(number) => number,
9740 None => serde_json::Value::Object(
9741 map.into_iter()
9742 .map(|(k, v)| (k, plain_numbers(v)))
9743 .collect(),
9744 ),
9745 },
9746 serde_json::Value::Array(items) => {
9747 serde_json::Value::Array(items.into_iter().map(plain_numbers).collect())
9748 }
9749 other => other,
9750 }
9751}
9752
9753fn accessor_to_json(accessor: &ValueAccessor<'_>) -> serde_json::Value {
9755 if accessor.is_null() {
9757 serde_json::Value::Null
9758 } else if let Ok(b) = accessor.boolean() {
9759 serde_json::Value::Bool(b)
9760 } else if let Ok(i) = accessor.i64() {
9761 serde_json::Value::Number(i.into())
9762 } else if let Ok(f) = accessor.f64() {
9763 serde_json::Number::from_f64(f)
9764 .map(serde_json::Value::Number)
9765 .unwrap_or(serde_json::Value::Null)
9766 } else if let Ok(s) = accessor.string() {
9767 serde_json::Value::String(s.to_string())
9768 } else if let Ok(name) = accessor.enum_name() {
9769 serde_json::Value::String(name.to_string())
9772 } else if let Ok(list) = accessor.list() {
9773 serde_json::Value::Array(list.iter().map(|v| accessor_to_json(&v)).collect())
9774 } else if let Ok(obj) = accessor.object() {
9775 let map: serde_json::Map<String, serde_json::Value> = obj
9776 .iter()
9777 .map(|(k, v)| (k.to_string(), accessor_to_json(&v)))
9778 .collect();
9779 match number_kept_as_text(&map) {
9780 Some(number) => number,
9781 None => serde_json::Value::Object(map),
9782 }
9783 } else {
9784 serde_json::Value::Null
9785 }
9786}
9787
9788fn value_to_json(value: &Value) -> serde_json::Value {
9790 match value {
9791 Value::Null => serde_json::Value::Null,
9792 Value::Boolean(b) => serde_json::Value::Bool(*b),
9793 Value::Number(n) => {
9794 if let Some(i) = n.as_i64() {
9795 serde_json::Value::Number(i.into())
9796 } else if let Some(f) = n.as_f64() {
9797 serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
9798 } else {
9799 serde_json::Value::Null
9800 }
9801 }
9802 Value::String(s) => serde_json::Value::String(s.clone()),
9803 Value::List(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
9804 Value::Object(obj) => {
9805 let map: serde_json::Map<String, serde_json::Value> = obj
9806 .iter()
9807 .map(|(k, v)| (k.to_string(), value_to_json(v)))
9808 .collect();
9809 serde_json::Value::Object(map)
9810 }
9811 Value::Binary(b) => serde_json::Value::String(base64::Engine::encode(
9812 &base64::engine::general_purpose::STANDARD,
9813 b,
9814 )),
9815 Value::Enum(e) => serde_json::Value::String(e.to_string()),
9816 }
9817}
9818
9819fn json_to_value(json: serde_json::Value) -> Value {
9821 match json {
9822 serde_json::Value::Null => Value::Null,
9823 serde_json::Value::Bool(b) => Value::Boolean(b),
9824 serde_json::Value::Number(n) => {
9825 if let Some(i) = n.as_i64() {
9826 Value::Number(i.into())
9827 } else if let Some(f) = n.as_f64() {
9828 Value::Number(async_graphql::Number::from_f64(f).unwrap())
9829 } else {
9830 Value::Null
9831 }
9832 }
9833 serde_json::Value::String(s) => Value::String(s),
9834 serde_json::Value::Array(arr) => Value::List(arr.into_iter().map(json_to_value).collect()),
9835 serde_json::Value::Object(obj) => {
9836 let map: indexmap::IndexMap<async_graphql::Name, Value> = obj
9837 .into_iter()
9838 .map(|(k, v)| (async_graphql::Name::new(k), json_to_value(v)))
9839 .collect();
9840 Value::Object(map)
9841 }
9842 }
9843}
9844
9845#[cfg(test)]
9855mod tests {
9856 use super::*;
9857 use indexmap::IndexMap;
9858 use postrust_core::schema_cache::{Cardinality, Column, Relationship, Table};
9859 use postrust_core::QualifiedIdentifier;
9860 use std::collections::{HashMap, HashSet};
9861
9862 fn entity_key_column(pg_type: &str, explicit_id: bool) -> EntityKeyColumn {
9863 EntityKeyColumn {
9864 field_name: "id".to_string(),
9865 column_name: "id".to_string(),
9866 pg_type: pg_type.to_string(),
9867 explicit_id,
9868 }
9869 }
9870
9871 #[test]
9872 fn federation_integer_keys_reject_strings() {
9873 let column = entity_key_column("int4", false);
9874
9875 assert_eq!(
9876 normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap(),
9877 serde_json::json!(2)
9878 );
9879 let error = normalize_entity_key_value("users", &column, &serde_json::json!("2"))
9880 .expect_err("a GraphQL Int cannot be a string");
9881 assert_eq!(
9882 error.message,
9883 "entity representation for \"users\" has invalid key field \"id\": expected a value compatible with PostgreSQL type \"int4\", found a string"
9884 );
9885 }
9886
9887 #[test]
9888 fn federation_bigint_keys_accept_exact_decimal_strings() {
9889 let column = entity_key_column("int8", false);
9890
9891 assert_eq!(
9892 normalize_entity_key_value("users", &column, &serde_json::json!(7)).unwrap(),
9893 serde_json::json!(7)
9894 );
9895 assert_eq!(
9896 normalize_entity_key_value("users", &column, &serde_json::json!("9007199254740993"))
9897 .unwrap(),
9898 serde_json::json!(9_007_199_254_740_993_i64)
9899 );
9900 assert!(normalize_entity_key_value(
9901 "users",
9902 &column,
9903 &serde_json::json!("9223372036854775808")
9904 )
9905 .is_err());
9906 assert!(normalize_entity_key_value("users", &column, &serde_json::json!("7.5")).is_err());
9907 }
9908
9909 #[test]
9910 fn federation_numeric_keys_accept_exact_decimal_strings() {
9911 let column = entity_key_column("numeric", false);
9912
9913 assert_eq!(
9914 normalize_entity_key_value("users", &column, &serde_json::json!(7.5)).unwrap(),
9915 serde_json::json!("7.5")
9916 );
9917 assert_eq!(
9918 normalize_entity_key_value(
9919 "users",
9920 &column,
9921 &serde_json::json!("9007199254740993.123456789")
9922 )
9923 .unwrap(),
9924 serde_json::json!("9007199254740993.123456789")
9925 );
9926 assert!(
9927 normalize_entity_key_value("users", &column, &serde_json::json!("not-a-number"))
9928 .is_err()
9929 );
9930 }
9931
9932 #[test]
9933 fn federation_integer_backed_ids_have_one_normal_form() {
9934 let column = entity_key_column("int4", true);
9935
9936 let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
9937 let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
9938 assert_eq!(number, serde_json::json!(2));
9939 assert_eq!(string, number);
9940 assert!(normalize_entity_key_value("users", &column, &serde_json::json!("two")).is_err());
9941 }
9942
9943 #[test]
9944 fn federation_text_backed_ids_normalize_integer_input_to_text() {
9945 let column = entity_key_column("text", true);
9946
9947 let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
9948 let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
9949 assert_eq!(number, serde_json::json!("2"));
9950 assert_eq!(string, number);
9951 }
9952
9953 #[test]
9954 fn federation_uuid_backed_ids_are_validated_and_canonicalized() {
9955 let column = entity_key_column("uuid", true);
9956
9957 assert_eq!(
9958 normalize_entity_key_value(
9959 "users",
9960 &column,
9961 &serde_json::json!("550E8400-E29B-41D4-A716-446655440000")
9962 )
9963 .unwrap(),
9964 serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
9965 );
9966 assert!(
9967 normalize_entity_key_value("users", &column, &serde_json::json!("not-a-uuid")).is_err()
9968 );
9969 }
9970
9971 fn create_test_table(name: &str) -> Table {
9972 let mut columns = IndexMap::new();
9973 columns.insert(
9974 "id".into(),
9975 Column {
9976 name: "id".into(),
9977 description: None,
9978 nullable: false,
9979 data_type: "integer".into(),
9980 nominal_type: "int4".into(),
9981 max_len: None,
9982 default: Some("nextval('id_seq')".into()),
9983 enum_values: vec![],
9984 is_pk: true,
9985 position: 1,
9986 always_generated: false,
9987 domain_type: None,
9988 },
9989 );
9990 columns.insert(
9991 "name".into(),
9992 Column {
9993 name: "name".into(),
9994 description: None,
9995 nullable: false,
9996 data_type: "text".into(),
9997 nominal_type: "text".into(),
9998 max_len: None,
9999 default: None,
10000 enum_values: vec![],
10001 is_pk: false,
10002 position: 2,
10003 always_generated: false,
10004 domain_type: None,
10005 },
10006 );
10007
10008 Table {
10009 schema: "public".into(),
10010 name: name.into(),
10011 description: None,
10012 is_view: false,
10013 insertable: true,
10014 updatable: true,
10015 deletable: true,
10016 pk_cols: vec!["id".into()],
10017 unique_constraints: Vec::new(),
10018 columns,
10019 computed_columns: Default::default(),
10020 is_partitioned: false,
10021 }
10022 }
10023
10024 fn create_test_schema_cache() -> SchemaCache {
10025 let mut tables = HashMap::new();
10026 let users = create_test_table("users");
10027 tables.insert(users.qualified_identifier(), users);
10028
10029 SchemaCache {
10030 tables,
10031 relationships: HashMap::new(),
10032 routines: HashMap::new(),
10033 timezones: HashSet::new(),
10034 media_handlers: HashMap::new(),
10035 pg_version: 150000,
10036 representations: Default::default(),
10037 }
10038 }
10039
10040 fn add_test_table(cache: &mut SchemaCache, name: &str) {
10041 let table = create_test_table(name);
10042 cache.tables.insert(table.qualified_identifier(), table);
10043 }
10044
10045 fn add_test_posts_relationship(cache: &mut SchemaCache) {
10046 let users = QualifiedIdentifier::new("public", "users");
10047 cache.relationships.insert(
10048 (users.clone(), "public".into()),
10049 vec![Relationship::ForeignKey {
10050 table: users,
10051 foreign_table: QualifiedIdentifier::new("public", "posts"),
10052 is_self: false,
10053 cardinality: Cardinality::O2M {
10054 constraint: "posts_user_id_fkey".into(),
10055 columns: vec![("id".into(), "user_id".into())],
10056 },
10057 table_is_view: false,
10058 foreign_table_is_view: false,
10059 constraint_name: "posts_user_id_fkey".into(),
10060 }],
10061 );
10062 }
10063
10064 #[test]
10069 fn test_graphql_type_ref_simple() {
10070 let _type_ref = graphql_type_ref("String");
10071 }
10073
10074 #[test]
10075 fn test_graphql_type_ref_non_null() {
10076 let _type_ref = graphql_type_ref("String!");
10077 }
10078
10079 #[test]
10080 fn test_graphql_type_ref_list() {
10081 let _type_ref = graphql_type_ref("[String]");
10082 }
10083
10084 #[test]
10085 fn test_graphql_type_ref_list_non_null() {
10086 let _type_ref = graphql_type_ref("[String!]!");
10087 }
10088
10089 #[test]
10094 fn test_value_to_json_null() {
10095 let value = Value::Null;
10096 let json = value_to_json(&value);
10097 assert_eq!(json, serde_json::Value::Null);
10098 }
10099
10100 #[test]
10101 fn test_value_to_json_boolean() {
10102 let value = Value::Boolean(true);
10103 let json = value_to_json(&value);
10104 assert_eq!(json, serde_json::Value::Bool(true));
10105 }
10106
10107 #[test]
10108 fn test_value_to_json_number() {
10109 let value = Value::Number(42.into());
10110 let json = value_to_json(&value);
10111 assert_eq!(json, serde_json::json!(42));
10112 }
10113
10114 #[test]
10115 fn test_value_to_json_string() {
10116 let value = Value::String("hello".to_string());
10117 let json = value_to_json(&value);
10118 assert_eq!(json, serde_json::Value::String("hello".to_string()));
10119 }
10120
10121 #[test]
10122 fn test_value_to_json_list() {
10123 let value = Value::List(vec![Value::Number(1.into()), Value::Number(2.into())]);
10124 let json = value_to_json(&value);
10125 assert_eq!(json, serde_json::json!([1, 2]));
10126 }
10127
10128 #[test]
10129 fn test_json_to_value_null() {
10130 let json = serde_json::Value::Null;
10131 let value = json_to_value(json);
10132 assert!(matches!(value, Value::Null));
10133 }
10134
10135 #[test]
10136 fn test_json_to_value_boolean() {
10137 let json = serde_json::Value::Bool(false);
10138 let value = json_to_value(json);
10139 assert!(matches!(value, Value::Boolean(false)));
10140 }
10141
10142 #[test]
10143 fn test_json_to_value_number() {
10144 let json = serde_json::json!(123);
10145 let value = json_to_value(json);
10146 assert!(matches!(value, Value::Number(_)));
10147 }
10148
10149 #[test]
10150 fn test_json_to_value_string() {
10151 let json = serde_json::Value::String("test".to_string());
10152 let value = json_to_value(json);
10153 assert!(matches!(value, Value::String(_)));
10154 }
10155
10156 #[test]
10157 fn test_json_to_value_array() {
10158 let json = serde_json::json!([1, 2, 3]);
10159 let value = json_to_value(json);
10160 assert!(matches!(value, Value::List(_)));
10161 }
10162
10163 #[test]
10164 fn test_json_to_value_object() {
10165 let json = serde_json::json!({"key": "value"});
10166 let value = json_to_value(json);
10167 assert!(matches!(value, Value::Object(_)));
10168 }
10169
10170 #[test]
10175 fn test_build_dynamic_schema() {
10176 let cache = create_test_schema_cache();
10177 let config = SchemaConfig::default();
10178 let generated = build_schema(&cache, &config);
10179
10180 let result = build_dynamic_schema(
10181 &generated,
10182 &cache,
10183 None,
10184 None,
10185 Arc::new(Default::default()),
10186 std::time::Duration::from_secs(30),
10187 None,
10188 );
10189 if let Err(ref e) = result {
10190 eprintln!("Schema build error: {:?}", e);
10191 }
10192 assert!(result.is_ok(), "Schema build failed: {:?}", result.err());
10193 }
10194
10195 #[test]
10196 fn a_column_type_override_reaches_fields_and_key_arguments() {
10197 let cache = create_test_schema_cache();
10198 let names = crate::names::NameOverrides::parse(
10199 r#"{"tables": {"public.users": {"column_types": {"id": "ID"}}}}"#,
10200 )
10201 .unwrap();
10202 let config = SchemaConfig {
10203 names: names.clone(),
10204 ..SchemaConfig::default()
10205 };
10206 let generated = build_schema(&cache, &config);
10207 let schema = build_dynamic_schema(
10208 &generated,
10209 &cache,
10210 None,
10211 None,
10212 Arc::new(names),
10213 std::time::Duration::from_secs(30),
10214 None,
10215 )
10216 .expect("schema with an ID override should build");
10217 let sdl = schema.sdl();
10218
10219 let input_body = |name: &str| {
10220 sdl.split_once(&format!("input {name} {{"))
10221 .and_then(|(_, rest)| rest.split_once('}'))
10222 .map(|(body, _)| body.to_string())
10223 .unwrap_or_else(|| panic!("missing input {name}:\n{sdl}"))
10224 };
10225
10226 assert!(sdl.contains("id: ID!"), "object and input fields:\n{sdl}");
10227 assert!(
10228 sdl.contains("users_by_pk(id: ID!): users"),
10229 "query key argument:\n{sdl}"
10230 );
10231 assert!(
10232 sdl.contains("delete_users_by_pk(id: ID!): users"),
10233 "delete key argument:\n{sdl}"
10234 );
10235 assert!(
10236 input_body("users_pk_columns_input").contains("id: ID!"),
10237 "update key input:\n{sdl}"
10238 );
10239 assert!(
10240 input_body("users_bool_exp").contains("id: ID_comparison_exp"),
10241 "comparison input:\n{sdl}"
10242 );
10243 assert!(
10244 input_body("users_insert_input").contains("id: ID"),
10245 "write input:\n{sdl}"
10246 );
10247 }
10248
10249 #[test]
10250 fn a_type_prefix_names_the_complete_dynamic_schema() {
10251 let cache = create_test_schema_cache();
10252 let config = SchemaConfig {
10253 type_prefix: Some("test".into()),
10254 ..SchemaConfig::default()
10255 };
10256 let generated = build_schema(&cache, &config);
10257 let schema = build_dynamic_schema(
10258 &generated,
10259 &cache,
10260 None,
10261 None,
10262 Arc::new(Default::default()),
10263 std::time::Duration::from_secs(30),
10264 None,
10265 )
10266 .expect("a prefixed schema should build");
10267 let sdl = schema.sdl();
10268
10269 assert!(sdl.contains("type test_users "), "object type:\n{sdl}");
10270 assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10271 assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10272 assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10273 assert!(
10274 sdl.contains("test_users_aggregate"),
10275 "aggregate type:\n{sdl}"
10276 );
10277 assert!(sdl.contains("test_insert_users("), "mutation root:\n{sdl}");
10278 }
10279
10280 #[test]
10281 fn federation_is_absent_by_default() {
10282 let cache = create_test_schema_cache();
10283 let generated = build_schema(&cache, &SchemaConfig::default());
10284 let schema = build_dynamic_schema(
10285 &generated,
10286 &cache,
10287 None,
10288 None,
10289 Arc::new(Default::default()),
10290 std::time::Duration::from_secs(30),
10291 None,
10292 )
10293 .expect("a non-federated schema should build");
10294 let sdl = schema.sdl();
10295
10296 assert!(!sdl.contains("_Service"), "service type:\n{sdl}");
10297 assert!(!sdl.contains("_service"), "service field:\n{sdl}");
10298 assert!(!sdl.contains("@key"), "federation key:\n{sdl}");
10299 }
10300
10301 #[test]
10302 fn federation_entity_selections_respect_fragment_type_conditions() {
10303 let document = async_graphql::parser::parse_query(
10304 r#"
10305 query {
10306 _entities(representations: []) {
10307 __typename
10308 ... on authors { id items { title } }
10309 ...ShopFields
10310 ... { common }
10311 ...EntityName
10312 }
10313 }
10314 fragment ShopFields on shops { id items { sku } }
10315 fragment EntityName on _Entity { __typename }
10316 "#,
10317 )
10318 .expect("query parses");
10319 let (_, operation) = document.operations.iter().next().expect("one operation");
10320 let entity = operation
10321 .node
10322 .selection_set
10323 .node
10324 .items
10325 .iter()
10326 .find_map(|selection| match &selection.node {
10327 Selection::Field(field) if field.node.name.node == "_entities" => Some(&field.node),
10328 _ => None,
10329 })
10330 .expect("_entities field");
10331
10332 assert_eq!(
10333 entity_field_applicability(&entity.selection_set.node, &document.fragments, "authors"),
10334 vec![true, true, true, false, false, true, true]
10335 );
10336 assert_eq!(
10337 entity_field_applicability(&entity.selection_set.node, &document.fragments, "shops"),
10338 vec![true, false, false, true, true, true, true]
10339 );
10340 }
10341
10342 #[tokio::test]
10343 async fn federation_entity_lists_allow_null_members() {
10344 let entity =
10345 Object::new("widgets").field(Field::new("id", TypeRef::named_nn(TypeRef::INT), |_| {
10346 FieldFuture::new(async { Ok(Some(Value::from(2))) })
10347 }));
10348 let union = Union::new("_Entity").possible_type("widgets");
10349 let query = Object::new("Query").field(Field::new(
10350 "_entities",
10351 TypeRef::named_list_nn("_Entity"),
10352 |_| {
10353 FieldFuture::new(async {
10354 Ok(Some(FieldValue::list([
10355 FieldValue::NULL,
10356 FieldValue::NULL.with_type("widgets"),
10357 ])))
10358 })
10359 },
10360 ));
10361 let schema = Schema::build("Query", None, None)
10362 .extension(NullableFederationEntity)
10363 .register(entity)
10364 .register(union)
10365 .register(query)
10366 .finish()
10367 .expect("schema builds");
10368
10369 let response = schema
10370 .execute("{ _entities { ... on widgets { id } } }")
10371 .await;
10372
10373 assert!(response.errors.is_empty(), "{:?}", response.errors);
10374 assert_eq!(
10375 response.data,
10376 async_graphql::value!({ "_entities": [null, { "id": 2 }] })
10377 );
10378 }
10379
10380 #[test]
10381 fn federation_metadata_is_byte_for_byte_inert_when_disabled() {
10382 let cache = create_test_schema_cache();
10383 let sdl_for = |config: SchemaConfig| {
10384 let generated = build_schema(&cache, &config);
10385 build_dynamic_schema(
10386 &generated,
10387 &cache,
10388 None,
10389 None,
10390 Arc::new(config.names.clone()),
10391 std::time::Duration::from_secs(30),
10392 None,
10393 )
10394 .expect("schema should build")
10395 .sdl()
10396 };
10397
10398 let baseline = sdl_for(SchemaConfig {
10399 type_prefix: Some("test".into()),
10400 ..SchemaConfig::default()
10401 });
10402 let with_disabled_federation_metadata = sdl_for(SchemaConfig {
10403 enable_federation: false,
10404 type_prefix: Some("test".into()),
10405 names: crate::names::NameOverrides::parse(
10406 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10407 )
10408 .expect("metadata"),
10409 ..SchemaConfig::default()
10410 });
10411
10412 assert_eq!(with_disabled_federation_metadata, baseline);
10413 assert!(
10414 with_disabled_federation_metadata.contains("type test_users "),
10415 "prefixed object type remains when federation is disabled:\n{with_disabled_federation_metadata}"
10416 );
10417 assert!(
10418 !with_disabled_federation_metadata.contains("_Service"),
10419 "service type:\n{with_disabled_federation_metadata}"
10420 );
10421 assert!(
10422 !with_disabled_federation_metadata.contains("@key"),
10423 "federation key:\n{with_disabled_federation_metadata}"
10424 );
10425 }
10426
10427 #[test]
10428 fn shared_entities_make_relationship_fields_shareable() {
10429 let federation_sdl = |shared: bool| {
10430 let mut cache = create_test_schema_cache();
10431 add_test_table(&mut cache, "posts");
10432 add_test_posts_relationship(&mut cache);
10433 let names = match shared {
10434 true => crate::names::NameOverrides::parse(
10435 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10436 )
10437 .expect("metadata"),
10438 false => crate::names::NameOverrides::default(),
10439 };
10440 let config = SchemaConfig {
10441 enable_federation: true,
10442 type_prefix: Some("test".into()),
10443 names: names.clone(),
10444 ..SchemaConfig::default()
10445 };
10446 let generated = build_schema(&cache, &config);
10447 build_dynamic_schema(
10448 &generated,
10449 &cache,
10450 None,
10451 None,
10452 Arc::new(names),
10453 std::time::Duration::from_secs(30),
10454 None,
10455 )
10456 .expect("federated schema should build")
10457 .sdl_with_options(async_graphql::SDLExportOptions::new().federation())
10458 };
10459
10460 let shared = federation_sdl(true);
10461 assert!(
10462 shared.contains("): [test_posts!]! @shareable"),
10463 "shared relationship field:\n{shared}"
10464 );
10465 assert!(
10466 shared.contains("): test_posts_aggregate! @shareable"),
10467 "shared relationship aggregate field:\n{shared}"
10468 );
10469
10470 let not_shared = federation_sdl(false);
10471 assert!(
10472 not_shared.contains("): [test_posts!]!"),
10473 "non-shared relationship field:\n{not_shared}"
10474 );
10475 assert!(
10476 !not_shared.contains("): [test_posts!]! @shareable"),
10477 "non-shared relationship field must not be shareable:\n{not_shared}"
10478 );
10479 assert!(
10480 not_shared.contains("): test_posts_aggregate!"),
10481 "non-shared relationship aggregate field:\n{not_shared}"
10482 );
10483 assert!(
10484 !not_shared.contains("): test_posts_aggregate! @shareable"),
10485 "non-shared relationship aggregate must not be shareable:\n{not_shared}"
10486 );
10487 }
10488
10489 #[tokio::test]
10490 async fn federation_exposes_service_sdl_and_entity_keys() {
10491 let mut cache = create_test_schema_cache();
10492 add_test_table(&mut cache, "posts");
10493 let config = SchemaConfig {
10494 enable_federation: true,
10495 type_prefix: Some("test".into()),
10496 names: crate::names::NameOverrides::parse(
10497 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10498 )
10499 .expect("metadata"),
10500 ..SchemaConfig::default()
10501 };
10502 let generated = build_schema(&cache, &config);
10503 let schema = build_dynamic_schema(
10504 &generated,
10505 &cache,
10506 None,
10507 None,
10508 Arc::new(Default::default()),
10509 std::time::Duration::from_secs(30),
10510 None,
10511 )
10512 .expect("a federated schema should build");
10513 let sdl = schema.sdl();
10514 let federation_sdl =
10515 schema.sdl_with_options(async_graphql::SDLExportOptions::new().federation());
10516
10517 assert!(sdl.contains("_Service"), "service type:\n{sdl}");
10518 assert!(sdl.contains("_service"), "service field:\n{sdl}");
10519 assert!(
10520 federation_sdl.contains("type users @key(fields: \"id\")"),
10521 "users key:\n{federation_sdl}"
10522 );
10523 assert!(
10524 federation_sdl.contains("@shareable"),
10525 "shared field directive:\n{federation_sdl}"
10526 );
10527 assert!(sdl.contains("type users "), "shared object type:\n{sdl}");
10528 assert!(
10529 !sdl.contains("type test_users "),
10530 "prefixed shared object type:\n{sdl}"
10531 );
10532 assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10533 assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10534 assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10535 assert!(
10536 !sdl.contains("input users_bool_exp "),
10537 "unprefixed derived input:\n{sdl}"
10538 );
10539 assert!(
10540 sdl.contains("test_users_aggregate"),
10541 "aggregate type:\n{sdl}"
10542 );
10543 assert!(
10544 !sdl.contains("type users_aggregate "),
10545 "unprefixed aggregate type:\n{sdl}"
10546 );
10547 assert!(
10548 sdl.contains("test_users_mutation_response"),
10549 "mutation response type:\n{sdl}"
10550 );
10551 assert!(
10552 !sdl.contains("type users_mutation_response "),
10553 "unprefixed mutation response type:\n{sdl}"
10554 );
10555 assert!(
10556 sdl.contains("type test_posts {"),
10557 "non-shared object type remains prefixed:\n{sdl}"
10558 );
10559 assert!(
10560 !sdl.contains("type posts {"),
10561 "non-shared object type must not become unprefixed:\n{sdl}"
10562 );
10563 assert!(sdl.contains("test_posts("), "prefixed posts root:\n{sdl}");
10564 assert!(
10565 sdl.contains("test_posts_bool_exp"),
10566 "prefixed posts bool_exp:\n{sdl}"
10567 );
10568
10569 let response = schema.execute("{ _service { sdl } }").await;
10570 assert!(
10571 response.errors.is_empty(),
10572 "service query errors: {:?}",
10573 response.errors
10574 );
10575 let service = response.data.into_json().expect("service response JSON");
10576 let service_sdl = service
10577 .get("_service")
10578 .and_then(|value| value.get("sdl"))
10579 .and_then(serde_json::Value::as_str)
10580 .expect("service SDL string");
10581 assert!(
10582 service_sdl.contains("type Query"),
10583 "federation root type:\n{service_sdl}"
10584 );
10585 assert!(
10586 service_sdl.contains("test_users("),
10587 "query fields are on the federation root:\n{service_sdl}"
10588 );
10589 assert!(
10590 service_sdl.contains("test_posts("),
10591 "non-shared query fields are on the federation root:\n{service_sdl}"
10592 );
10593 assert!(
10594 !service_sdl.contains("type query_root"),
10595 "Apollo composition treats query_root as an ordinary object without a schema definition:\n{service_sdl}"
10596 );
10597 assert!(
10598 service_sdl.contains("type users @key(fields: \"id\")"),
10599 "{service_sdl}"
10600 );
10601 assert!(
10602 service_sdl.contains("type test_posts @key(fields: \"id\")"),
10603 "{service_sdl}"
10604 );
10605 }
10606
10607 #[test]
10610 fn exists_is_a_subselect_over_another_table() {
10611 let cache = create_test_schema_cache();
10612 let names = crate::names::NameOverrides::default();
10613 let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10614 let scope = WhereScope::table("public", "users", "users", &names)
10615 .with_resolution(&cache, &relationships);
10616
10617 let mut param_idx = 1usize;
10618 let mut values: Vec<serde_json::Value> = Vec::new();
10619 let mut aliases = 0usize;
10620 let sql = build_condition(
10621 &serde_json::json!({
10622 "_exists": {"_table": "users", "_where": {"name": {"_eq": "Ann"}}}
10623 }),
10624 &scope,
10625 &mut param_idx,
10626 &mut values,
10627 &mut aliases,
10628 )
10629 .expect("compiles")
10630 .expect("constrains something");
10631
10632 assert_eq!(
10633 sql,
10634 "EXISTS (SELECT 1 FROM \"public\".\"users\" AS \"pgrst_exists_1\" \
10635 WHERE \"pgrst_exists_1\".\"name\" = $1::text)"
10636 );
10637 assert_eq!(values, vec![serde_json::json!("Ann")]);
10638 }
10639
10640 #[test]
10642 fn exists_names_a_table_the_server_has() {
10643 let cache = create_test_schema_cache();
10644 let names = crate::names::NameOverrides::default();
10645 let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10646 let scope = WhereScope::table("public", "users", "users", &names)
10647 .with_resolution(&cache, &relationships);
10648 let error = build_condition(
10649 &serde_json::json!({"_exists": {"_table": "nowhere", "_where": {}}}),
10650 &scope,
10651 &mut 1,
10652 &mut Vec::new(),
10653 &mut 0,
10654 )
10655 .expect_err("a table that is not there is an error");
10656 assert!(
10657 error.message.contains("public.nowhere"),
10658 "{}",
10659 error.message
10660 );
10661 }
10662
10663 #[test]
10670 fn a_table_a_role_may_count_and_not_read_has_a_count_and_no_rows() {
10671 let cache = create_test_schema_cache();
10672 let names = crate::names::NameOverrides::parse(
10673 r#"{"tables": {"public.users": {"permissions":
10674 {"counter": {"select": {"columns": [], "filter": {},
10675 "allow_aggregations": true}}}}}}"#,
10676 )
10677 .unwrap();
10678 let view = crate::role::cache_for_role(&cache, &names, "counter", false);
10679 assert!(!view.tables.is_empty(), "the table is there to be counted");
10680
10681 let config = SchemaConfig {
10682 names: names.clone(),
10683 role: Some("counter".to_string()),
10684 ..SchemaConfig::default()
10685 };
10686 let generated = build_schema(&view, &config);
10687 let schema = build_dynamic_schema(
10688 &generated,
10689 &view,
10690 None,
10691 None,
10692 Arc::new(names),
10693 std::time::Duration::from_secs(30),
10694 Some("counter"),
10695 )
10696 .expect("a role that may only count still has a buildable schema");
10697 let sdl = schema.sdl();
10698
10699 assert!(
10700 sdl.contains("users_aggregate"),
10701 "the count is there:\n{}",
10702 sdl
10703 );
10704 assert!(sdl.contains("count: Int!"), "and it is a count:\n{}", sdl);
10705 assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10707 assert!(
10708 !sdl.contains("nodes"),
10709 "and no rows beside the numbers:\n{}",
10710 sdl
10711 );
10712 assert!(
10713 !sdl.contains("users_select_column"),
10714 "no column enum to order by or count over:\n{}",
10715 sdl
10716 );
10717 assert!(!sdl.contains("users_max_fields"), "no max:\n{}", sdl);
10719 }
10720
10721 #[test]
10728 fn a_table_a_role_may_only_write_gets_the_write_and_no_type() {
10729 let cache = create_test_schema_cache();
10730 let names = crate::names::NameOverrides::parse(
10731 r#"{"tables": {"public.users": {"permissions":
10732 {"writer": {"insert": {"columns": "*", "check": {}}}}}}}"#,
10733 )
10734 .unwrap();
10735 let view = crate::role::cache_for_role(&cache, &names, "writer", false);
10736 let config = SchemaConfig {
10737 names: names.clone(),
10738 role: Some("writer".to_string()),
10739 ..SchemaConfig::default()
10740 };
10741 let generated = build_schema(&view, &config);
10742
10743 let schema = build_dynamic_schema(
10744 &generated,
10745 &view,
10746 None,
10747 None,
10748 Arc::new(names),
10749 std::time::Duration::from_secs(30),
10750 Some("writer"),
10751 )
10752 .expect("a role that may only write still has a buildable schema");
10753 let sdl = schema.sdl();
10754
10755 assert!(
10756 sdl.contains("insert_users("),
10757 "the write is there:\n{}",
10758 sdl
10759 );
10760 assert!(
10761 sdl.contains("users_insert_input"),
10762 "and its input:\n{}",
10763 sdl
10764 );
10765 assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10767 assert!(!sdl.contains("insert_users_one"), "no insert_one:\n{}", sdl);
10768 assert!(
10769 !sdl.contains("returning"),
10770 "and nothing to return:\n{}",
10771 sdl
10772 );
10773 assert!(
10774 sdl.contains("users_mutation_response"),
10775 "the count is still answered:\n{}",
10776 sdl
10777 );
10778 assert!(
10779 !sdl.contains("_mutation_response_mutation_response"),
10780 "and no type is named twice over:\n{}",
10781 sdl
10782 );
10783 }
10784
10785 fn cache_with_a_mutation_function() -> SchemaCache {
10788 use postrust_core::schema_cache::{FuncVolatility, RetType, Routine};
10789
10790 let mut cache = create_test_schema_cache();
10791 let routine = Routine {
10792 schema: "public".into(),
10793 name: "add_to_score".into(),
10794 description: None,
10795 params: Vec::new(),
10796 return_type: RetType::SetOf("users".into()),
10797 returns_composite: true,
10798 volatility: FuncVolatility::Volatile,
10799 has_variadic: false,
10800 isolation_level: None,
10801 settings: Vec::new(),
10802 is_procedure: false,
10803 media_type: None,
10804 media_base_type: None,
10805 output_columns: Vec::new(),
10806 };
10807 cache
10808 .routines
10809 .insert(routine.qualified_identifier(), vec![routine]);
10810 cache
10811 }
10812
10813 #[test]
10818 fn a_mutation_function_needs_the_role_to_be_named() {
10819 let cache = cache_with_a_mutation_function();
10820 let withheld = crate::names::NameOverrides::parse(
10821 r#"{"tables": {"public.users": {"permissions":
10822 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10823 "functions": {"public.add_to_score": {"exposed_as": "mutation"}}}"#,
10824 )
10825 .unwrap();
10826 let granted = crate::names::NameOverrides::parse(
10827 r#"{"tables": {"public.users": {"permissions":
10828 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10829 "functions": {"public.add_to_score":
10830 {"exposed_as": "mutation", "roles": ["reader"]}}}"#,
10831 )
10832 .unwrap();
10833
10834 let sdl_for = |names: &crate::names::NameOverrides| {
10835 let view = crate::role::cache_for_role(&cache, names, "reader", false);
10836 let config = SchemaConfig {
10837 names: names.clone(),
10838 role: Some("reader".to_string()),
10839 ..SchemaConfig::default()
10840 };
10841 let generated = build_schema(&view, &config);
10842 build_dynamic_schema(
10843 &generated,
10844 &view,
10845 None,
10846 None,
10847 Arc::new(names.clone()),
10848 std::time::Duration::from_secs(30),
10849 Some("reader"),
10850 )
10851 .expect("schema builds")
10852 .sdl()
10853 };
10854
10855 let without = sdl_for(&withheld);
10856 assert!(
10857 !without.contains("add_to_score"),
10858 "not granted, so not there:\n{}",
10859 without
10860 );
10861 assert!(
10864 !without.contains("mutation: mutation_root"),
10865 "and no mutation root at all:\n{}",
10866 without
10867 );
10868
10869 let with = sdl_for(&granted);
10870 assert!(
10871 with.contains("add_to_score"),
10872 "named by a function permission, so it is:\n{}",
10873 with
10874 );
10875 }
10876
10877 #[test]
10879 fn a_role_the_document_never_names_is_granted_nothing() {
10880 let cache = Arc::new(create_test_schema_cache());
10881 let config = SchemaConfig {
10882 names: crate::names::NameOverrides::parse(
10883 r#"{"tables": {"public.users": {"permissions":
10884 {"reader": {"select": {"columns": "*", "filter": {}}}}}}}"#,
10885 )
10886 .unwrap(),
10887 ..SchemaConfig::default()
10888 };
10889 let empty = build_ungranted_schema(&cache, &config).expect("it builds");
10890 let sdl = empty.sdl();
10891 assert!(
10892 sdl.contains("no_queries_available"),
10893 "nothing to read:\n{}",
10894 sdl
10895 );
10896 assert!(!sdl.contains("type users "), "and no table:\n{}", sdl);
10897 assert!(build_ungranted_schema(&cache, &SchemaConfig::default()).is_none());
10899 }
10900
10901 #[test]
10904 fn a_column_a_role_may_set_without_seeing_is_in_the_input_alone() {
10905 let cache = create_test_schema_cache();
10906 let names = crate::names::NameOverrides::parse(
10907 r#"{"tables": {"public.users": {"permissions":
10908 {"user": {"select": {"columns": ["id"], "filter": {}},
10909 "insert": {"columns": "*", "check": {}}}}}}}"#,
10910 )
10911 .unwrap();
10912 let view = crate::role::cache_for_role(&cache, &names, "user", false);
10913 let config = SchemaConfig {
10914 names: names.clone(),
10915 role: Some("user".to_string()),
10916 ..SchemaConfig::default()
10917 };
10918 let generated = build_schema(&view, &config);
10919 let object = &generated.object_types["users"];
10920 assert_eq!(
10921 object
10922 .fields
10923 .iter()
10924 .map(|f| f.name.as_str())
10925 .collect::<Vec<_>>(),
10926 vec!["id"],
10927 "the type shows what may be read"
10928 );
10929 assert!(
10930 object.writable_fields.iter().any(|f| f.name == "name"),
10931 "and the input keeps what may be written"
10932 );
10933
10934 let schema = build_dynamic_schema(
10935 &generated,
10936 &view,
10937 None,
10938 None,
10939 Arc::new(names),
10940 std::time::Duration::from_secs(30),
10941 Some("user"),
10942 )
10943 .expect("schema builds");
10944 let sdl = schema.sdl();
10945 let input = sdl
10946 .split("input users_insert_input")
10947 .nth(1)
10948 .and_then(|rest| rest.split('}').next())
10949 .expect("the insert input is registered");
10950 assert!(input.contains("name:"), "settable without being readable");
10951 }
10952
10953 #[test]
10957 fn a_generated_column_is_readable_and_not_writable() {
10958 let mut cache = create_test_schema_cache();
10959 cache
10960 .tables
10961 .get_mut(&postrust_core::api_request::QualifiedIdentifier::new(
10962 "public", "users",
10963 ))
10964 .expect("the fixture has users")
10965 .columns
10966 .get_mut("id")
10967 .expect("with an id")
10968 .always_generated = true;
10969
10970 let names = crate::names::NameOverrides::default();
10971 let config = SchemaConfig {
10972 names: names.clone(),
10973 ..SchemaConfig::default()
10974 };
10975 let generated = build_schema(&cache, &config);
10976 let schema = build_dynamic_schema(
10977 &generated,
10978 &cache,
10979 None,
10980 None,
10981 Arc::new(names),
10982 std::time::Duration::from_secs(30),
10983 None,
10984 )
10985 .expect("schema builds");
10986 let sdl = schema.sdl();
10987
10988 let block = |header: &str| {
10989 sdl.split(header)
10990 .nth(1)
10991 .and_then(|rest| rest.split('}').next())
10992 .unwrap_or_else(|| panic!("{} is registered:\n{}", header, sdl))
10993 .to_string()
10994 };
10995 assert!(
10996 block("type users ").contains("id:"),
10997 "still readable:\n{}",
10998 sdl
10999 );
11000 for header in ["input users_insert_input", "input users_set_input"] {
11001 assert!(
11002 !block(header).contains("id:"),
11003 "{} may not name it:\n{}",
11004 header,
11005 block(header)
11006 );
11007 }
11008 assert!(
11011 !sdl.contains("users_inc_input"),
11012 "and nothing is left to increment:\n{}",
11013 sdl
11014 );
11015 assert!(
11016 block("input users_insert_input").contains("name:"),
11017 "and the rest of the table is untouched",
11018 );
11019 }
11020
11021 #[test]
11022 fn test_create_object_type() {
11023 let table = create_test_table("users");
11024 let obj = TableObjectType::from_table(&table);
11025 let _gql_obj = create_object_type(&obj, &[], None);
11026 }
11027
11028 #[test]
11029 fn test_create_query_type() {
11030 let cache = create_test_schema_cache();
11031 let config = SchemaConfig::default();
11032 let generated = build_schema(&cache, &config);
11033
11034 let _query = create_query_type(
11035 &generated,
11036 "query_root",
11037 None,
11038 Arc::new(HashMap::new()),
11039 Arc::new(Default::default()),
11040 );
11041 }
11042
11043 #[test]
11044 fn test_create_mutation_type() {
11045 let cache = create_test_schema_cache();
11046 let config = SchemaConfig::default();
11047 let generated = build_schema(&cache, &config);
11048
11049 let _mutation = create_mutation_type(
11050 &generated,
11051 "mutation_root",
11052 Arc::new(HashMap::new()),
11053 Arc::new(HashMap::new()),
11054 Arc::new(Default::default()),
11055 None,
11056 None,
11057 );
11058 }
11059
11060 #[test]
11065 fn a_scalar_is_named_the_way_a_client_declares_it() {
11066 use crate::types::{pg_type_to_graphql, GraphQLType};
11067
11068 assert_eq!(pg_type_to_graphql("jsonb").to_string(), "jsonb");
11073 assert_eq!(pg_type_to_graphql("json").to_string(), "json");
11074 assert_eq!(pg_type_to_graphql("int8").to_string(), "bigint");
11075 assert_eq!(pg_type_to_graphql("numeric").to_string(), "numeric");
11076 assert_eq!(pg_type_to_graphql("timestamptz").to_string(), "timestamptz");
11077 assert_eq!(pg_type_to_graphql("timestamp").to_string(), "timestamp");
11078 assert_eq!(pg_type_to_graphql("uuid").to_string(), "uuid");
11079
11080 assert_eq!(pg_type_to_graphql("geometry").to_string(), "geometry");
11083 assert_eq!(
11084 pg_type_to_graphql("colors_enum"),
11085 GraphQLType::Custom("colors_enum".to_string())
11086 );
11087
11088 assert_eq!(
11090 leaf_scalar_name(&pg_type_to_graphql("_text")),
11091 "String".to_string()
11092 );
11093 }
11094
11095 #[test]
11100 fn every_table_gets_a_boolean_expression() {
11101 let cache = create_test_schema_cache();
11102 let config = SchemaConfig::default();
11103 let generated = build_schema(&cache, &config);
11104
11105 let (inputs, _) = crate::input::bool_exp::build_inputs(
11106 &generated.object_types,
11107 &generated.relationship_fields,
11108 );
11109 let names: HashSet<String> = inputs.iter().map(|i| i.type_name().to_string()).collect();
11110
11111 for table in generated.object_types.keys() {
11112 let expected = format!("{}_bool_exp", table);
11113 assert!(
11114 names.contains(&expected),
11115 "no {} among {:?}",
11116 expected,
11117 names
11118 );
11119 }
11120 assert!(names.contains("String_comparison_exp"));
11122 assert!(names.contains("Int_comparison_exp"));
11123 }
11124
11125 #[test]
11126 fn a_schema_carrying_the_boolean_expressions_still_builds() {
11127 let cache = create_test_schema_cache();
11128 let config = SchemaConfig::default();
11129 let generated = build_schema(&cache, &config);
11130
11131 let schema = build_dynamic_schema(
11132 &generated,
11133 &cache,
11134 None,
11135 None,
11136 Arc::new(Default::default()),
11137 std::time::Duration::from_secs(30),
11138 None,
11139 );
11140 assert!(schema.is_ok(), "{:?}", schema.err());
11141
11142 let sdl = schema.unwrap().sdl();
11143 assert!(
11144 sdl.contains("_bool_exp"),
11145 "no boolean expressions in:\n{}",
11146 sdl
11147 );
11148 assert!(sdl.contains("_and"), "no _and in the generated expressions");
11149 }
11150
11151 #[test]
11156 fn test_build_schema_with_subscriptions() {
11157 let cache = create_test_schema_cache();
11158 let config = SchemaConfig {
11159 enable_subscriptions: true,
11160 ..SchemaConfig::default()
11161 };
11162 let generated = build_schema(&cache, &config);
11163
11164 let sub_fields = generate_subscription_fields(&cache, &generated);
11166 assert!(!sub_fields.is_empty(), "Should have subscription fields");
11167
11168 let result = build_dynamic_schema(
11170 &generated,
11171 &cache,
11172 Some(&sub_fields),
11173 None,
11174 Arc::new(Default::default()),
11175 std::time::Duration::from_secs(30),
11176 None,
11177 );
11178 assert!(result.is_ok(), "Schema with subscriptions should build");
11179 }
11180
11181 #[test]
11182 fn test_subscription_field_generation() {
11183 let cache = create_test_schema_cache();
11184 let config = SchemaConfig::default();
11185 let generated = build_schema(&cache, &config);
11186
11187 let fields = generate_subscription_fields(&cache, &generated);
11188
11189 assert_eq!(fields.len(), 1);
11191 assert_eq!(fields[0].name, "users");
11192 assert_eq!(fields[0].table_name, "users");
11193 assert_eq!(fields[0].channel_name(), "postrust_public_users");
11194 }
11195
11196 #[test]
11197 fn test_create_subscription_type() {
11198 let cache = create_test_schema_cache();
11199 let config = SchemaConfig::default();
11200 let generated = build_schema(&cache, &config);
11201 let _subscription = create_subscription_type(
11204 &generated,
11205 "subscription_root",
11206 None,
11207 Arc::new(Default::default()),
11208 Arc::new(Default::default()),
11209 std::time::Duration::from_secs(30),
11210 );
11211 }
11212}
11213
11214#[derive(Debug, PartialEq, Eq)]
11216enum PathStep {
11217 Key(String),
11218 Index(usize),
11219}
11220
11221fn walk_json_path(value: &Value, path: &str) -> Result<Value, async_graphql::Error> {
11228 let mut at = value;
11229 for step in parse_json_path(path)? {
11230 let next = match (&step, at) {
11231 (PathStep::Key(key), Value::Object(map)) => map.get(key.as_str()),
11232 (PathStep::Index(index), Value::List(items)) => items.get(*index),
11233 _ => None,
11236 };
11237 match next {
11238 Some(next) => at = next,
11239 None => return Ok(Value::Null),
11240 }
11241 }
11242 Ok(at.clone())
11243}
11244
11245fn unclosed_bracket(path: &str) -> async_graphql::Error {
11247 let message = format!("\"{}\" is not a JSON path: a bracket is never closed", path);
11248 validation_error(&message)
11249}
11250
11251fn parse_json_path(path: &str) -> Result<Vec<PathStep>, async_graphql::Error> {
11258 let mut steps = Vec::new();
11259 let mut chars = path.chars().peekable();
11260 if chars.peek() == Some(&'$') {
11262 chars.next();
11263 }
11264 while let Some(&c) = chars.peek() {
11265 match c {
11266 '.' => {
11268 chars.next();
11269 }
11270 '[' => {
11271 chars.next();
11272 let quote = match chars.peek() {
11273 Some(&q @ ('\'' | '"')) => {
11274 chars.next();
11275 Some(q)
11276 }
11277 _ => None,
11278 };
11279 let mut inner = String::new();
11280 loop {
11281 let Some(c) = chars.next() else {
11282 return Err(unclosed_bracket(path));
11283 };
11284 match (quote, c) {
11285 (Some(quote), c) if c == quote => break,
11286 (None, ']') => break,
11287 _ => inner.push(c),
11288 }
11289 }
11290 if quote.is_some() {
11292 match chars.next() {
11293 Some(']') => {}
11294 _ => return Err(unclosed_bracket(path)),
11295 }
11296 }
11297 match (quote, inner.parse::<usize>()) {
11300 (None, Ok(index)) => steps.push(PathStep::Index(index)),
11301 _ => steps.push(PathStep::Key(inner)),
11302 }
11303 }
11304 _ => {
11305 let mut key = String::new();
11306 while let Some(&c) = chars.peek() {
11307 if matches!(c, '.' | '[') {
11308 break;
11309 }
11310 key.push(c);
11311 chars.next();
11312 }
11313 steps.push(PathStep::Key(key));
11314 }
11315 }
11316 }
11317 Ok(steps)
11318}
11319
11320#[cfg(test)]
11321mod json_path_tests {
11322 use super::*;
11323
11324 fn steps(path: &str) -> Vec<PathStep> {
11325 parse_json_path(path).expect("the path parses")
11326 }
11327
11328 fn key(name: &str) -> PathStep {
11329 PathStep::Key(name.to_string())
11330 }
11331
11332 #[test]
11333 fn the_document_itself_has_no_steps() {
11334 assert_eq!(steps("$"), vec![]);
11335 assert_eq!(steps(""), vec![]);
11336 }
11337
11338 #[test]
11339 fn a_key_is_written_bare_or_after_a_dot() {
11340 assert_eq!(steps("a"), vec![key("a")]);
11341 assert_eq!(steps(".obj.c1"), vec![key("obj"), key("c1")]);
11342 assert_eq!(steps("._underscore"), vec![key("_underscore")]);
11343 }
11344
11345 #[test]
11346 fn a_bracket_holds_an_index_or_a_quoted_key() {
11347 assert_eq!(steps("arr[0]"), vec![key("arr"), PathStep::Index(0)]);
11348 assert_eq!(steps("['!@#$%^']"), vec![key("!@#$%^")]);
11349 assert_eq!(
11350 steps("translations['hello world!']"),
11351 vec![key("translations"), key("hello world!")]
11352 );
11353 }
11354
11355 #[test]
11357 fn a_dot_before_a_bracket_changes_nothing() {
11358 let expected = vec![key("objs"), PathStep::Index(0), key("\u{4f60}\u{597d}")];
11359 for spelling in [
11360 "objs[0]['\u{4f60}\u{597d}']",
11361 "objs[0][\"\u{4f60}\u{597d}\"]",
11362 "objs[0].[\"\u{4f60}\u{597d}\"]",
11363 "objs.[0].[\"\u{4f60}\u{597d}\"]",
11364 ] {
11365 assert_eq!(steps(spelling), expected, "{}", spelling);
11366 }
11367 }
11368
11369 #[test]
11370 fn a_bracket_that_never_closes_is_refused() {
11371 assert!(parse_json_path("arr[0").is_err());
11372 assert!(parse_json_path("['key'").is_err());
11373 }
11374
11375 #[test]
11376 fn a_step_the_document_does_not_have_reads_null() {
11377 let document = Value::from_json(serde_json::json!({
11378 "obj": {"c1": "c2"},
11379 "arr": [1, 2, 3],
11380 }))
11381 .expect("a document");
11382 let at = |path| walk_json_path(&document, path).expect("the path parses");
11383 assert_eq!(at(".obj.c1"), Value::String("c2".to_string()));
11384 assert_eq!(at("arr[1]"), Value::Number(2.into()));
11385 assert_eq!(at("arr[9]"), Value::Null);
11386 assert_eq!(at(".obj.nothing"), Value::Null);
11387 assert_eq!(at("obj[0]"), Value::Null);
11390 assert_eq!(at("arr.first"), Value::Null);
11391 }
11392}