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(
421 State(state): State<Arc<GraphQLState>>,
422 protocol: async_graphql_axum::GraphQLProtocol,
423 ws: axum::extract::WebSocketUpgrade,
424) -> impl IntoResponse {
425 let schema = state.schema.clone();
426 let pool = state.pool.clone();
427 let broker = Arc::clone(&state.broker);
428
429 ws.protocols(["graphql-transport-ws", "graphql-ws"])
430 .on_upgrade(move |socket| async move {
431 let mut data = async_graphql::Data::default();
432 data.insert(pool);
433 data.insert(broker);
434
435 async_graphql_axum::GraphQLWebSocket::new(socket, schema, protocol)
436 .with_data(data)
437 .serve()
438 .await
439 })
440}
441
442pub async fn graphql_playground() -> impl axum::response::IntoResponse {
444 axum::response::Html(async_graphql::http::playground_source(
445 async_graphql::http::GraphQLPlaygroundConfig::new("/graphql")
446 .subscription_endpoint("/graphql/ws"),
447 ))
448}
449
450fn column_for<'a>(
457 names: &'a crate::names::NameOverrides,
458 schema: &str,
459 table: &str,
460 field: &'a str,
461) -> &'a str {
462 names.column_source(schema, table, field).unwrap_or(field)
463}
464
465fn table_column_for<'a>(
467 names: &'a crate::names::NameOverrides,
468 table: &postrust_core::schema_cache::Table,
469 field: &'a str,
470) -> &'a str {
471 column_for(names, &table.schema, &table.name, field)
472}
473
474fn rename_projection(
482 table: &postrust_core::schema_cache::Table,
483 alias: &str,
484 names: &crate::names::NameOverrides,
485) -> Option<String> {
486 if !names.renames_columns(&table.schema, &table.name) {
487 return None;
488 }
489 let parts: Vec<String> = table
490 .columns
491 .values()
492 .map(|column| {
493 let exposed = names
494 .column(&table.schema, &table.name, &column.name)
495 .unwrap_or(&column.name);
496 format!(
497 "{}.{} AS {}",
498 postrust_sql::escape_ident(alias),
499 postrust_sql::escape_ident(&column.name),
500 postrust_sql::escape_ident(exposed)
501 )
502 })
503 .collect();
504 match parts.is_empty() {
505 true => None,
506 false => Some(parts.join(", ")),
507 }
508}
509
510fn exposed_column_types(
516 table: &postrust_core::schema_cache::Table,
517 names: &crate::names::NameOverrides,
518) -> HashMap<String, String> {
519 table
520 .columns
521 .values()
522 .map(|column| {
523 let exposed = names
524 .column(&table.schema, &table.name, &column.name)
525 .unwrap_or(&column.name);
526 (exposed.to_string(), column.nominal_type.clone())
527 })
528 .collect()
529}
530
531fn write_type_ref(graphql_type: &crate::types::GraphQLType) -> TypeRef {
537 match graphql_type {
538 crate::types::GraphQLType::List(inner) => TypeRef::named_list(leaf_scalar_name(inner)),
539 other => TypeRef::named(leaf_scalar_name(other)),
540 }
541}
542
543fn leaf_scalar_name(graphql_type: &crate::types::GraphQLType) -> String {
545 match graphql_type {
546 crate::types::GraphQLType::List(inner) => leaf_scalar_name(inner),
547 other => other.to_string(),
548 }
549}
550
551fn build_dynamic_schema(
553 generated: &GeneratedSchema,
554 _schema_cache: &SchemaCache,
555 subscription_fields: Option<&[SubField]>,
556 max_rows: Option<i64>,
557 names: Arc<crate::names::NameOverrides>,
558 subscription_refresh: std::time::Duration,
559 role: Option<&str>,
564) -> Result<Schema, GraphQLError> {
565 let mut object_types: HashMap<String, Object> = HashMap::new();
567
568 for (type_name, obj) in &generated.object_types {
573 if obj.fields.is_empty() {
574 continue;
575 }
576 let relationships = generated
577 .relationship_fields
578 .get(type_name)
579 .map(|r| r.as_slice())
580 .unwrap_or(&[]);
581 let table_obj = create_object_type(
582 obj,
583 relationships,
584 generated.federation_entities.get(type_name),
585 );
586 object_types.insert(type_name.clone(), table_obj);
587 }
588
589 let countable: HashSet<&str> = generated
595 .query_fields
596 .iter()
597 .filter(|field| field.aggregates && !field.is_by_pk)
598 .map(|field| field.type_name.as_str())
599 .collect();
600 for (type_name, obj) in &generated.object_types {
601 if obj.fields.is_empty() && !countable.contains(type_name.as_str()) {
602 continue;
603 }
604 for aggregate_type in create_aggregate_types(&obj.local_name, obj) {
605 object_types.insert(aggregate_type.type_name().to_string(), aggregate_type);
606 }
607 }
608
609 let mutable: HashMap<String, String> = generated
613 .mutation_fields
614 .iter()
615 .filter_map(|f| {
616 let response = f
624 .return_type
625 .trim_matches(|c| c == '[' || c == ']' || c == '!')
626 .strip_suffix("_mutation_response")?;
627 Some((response.to_string(), f.type_name.clone()))
628 })
629 .collect();
630 for (base_name, row_type_name) in mutable {
631 let returning = generated
635 .object_types
636 .get(&row_type_name)
637 .is_some_and(|object| !object.fields.is_empty());
638 object_types.insert(
639 mutation_response_type_name(&base_name),
640 create_mutation_response_type(&base_name, &row_type_name, returning),
641 );
642 }
643
644 let query_root = if generated.enable_federation {
649 "Query"
650 } else {
651 "query_root"
652 };
653 let mutation_root = if generated.enable_federation {
654 "Mutation"
655 } else {
656 "mutation_root"
657 };
658 let subscription_root = if generated.enable_federation {
659 "Subscription"
660 } else {
661 "subscription_root"
662 };
663
664 let relationships = Arc::new(generated.relationship_fields.clone());
667 let type_names: Arc<HashMap<(String, String), String>> = Arc::new(
673 generated
674 .object_types
675 .iter()
676 .map(|(type_name, object)| {
677 (
678 (object.table.schema.clone(), object.table.name.clone()),
679 type_name.clone(),
680 )
681 })
682 .collect(),
683 );
684 let query = add_function_fields(
685 create_query_type(
686 generated,
687 query_root,
688 max_rows,
689 Arc::clone(&relationships),
690 Arc::clone(&names),
691 ),
692 generated,
693 false,
694 max_rows,
695 Arc::clone(&relationships),
696 Arc::clone(&names),
697 );
698
699 let mutation = if !generated.mutation_fields.is_empty()
701 || generated.function_fields.iter().any(|f| f.volatile)
702 {
703 Some(add_function_fields(
704 create_mutation_type(
705 generated,
706 mutation_root,
707 Arc::clone(&relationships),
708 Arc::clone(&type_names),
709 Arc::clone(&names),
710 max_rows,
711 role,
712 ),
713 generated,
714 true,
715 max_rows,
716 Arc::clone(&relationships),
717 Arc::clone(&names),
718 ))
719 } else {
720 None
721 };
722
723 let subscription = subscription_fields.map(|_| {
725 create_subscription_type(
726 generated,
727 subscription_root,
728 max_rows,
729 Arc::clone(&relationships),
730 Arc::clone(&names),
731 subscription_refresh,
732 )
733 });
734
735 let mut builder = Schema::build(
737 query_root,
738 mutation.as_ref().map(|_| mutation_root),
739 subscription.as_ref().map(|_| subscription_root),
740 );
741 if generated.enable_federation {
742 builder = builder
743 .enable_federation()
744 .extension(NullableFederationEntity);
745 let entity_state = Arc::new(EntityResolverState::new(
746 generated,
747 Arc::clone(&relationships),
748 Arc::clone(&names),
749 max_rows,
750 ));
751 builder = builder.entity_resolver(move |ctx| {
752 let state = Arc::clone(&entity_state);
753 FieldFuture::new(async move { resolve_entities(&ctx, state.as_ref()).await })
754 });
755 }
756
757 for (_, obj) in object_types {
759 builder = builder.register(obj);
760 }
761
762 builder = builder.register(query);
764
765 if let Some(mutation) = mutation {
767 builder = builder.register(mutation);
768 }
769
770 if let Some(subscription) = subscription {
772 builder = builder.register(subscription);
773 }
774
775 let (bool_exp_inputs, bool_exp_scalars) = crate::input::bool_exp::build_inputs(
782 &generated.object_types,
783 &generated.relationship_fields,
784 );
785 for input in bool_exp_inputs {
786 builder = builder.register(input);
787 }
788
789 let mut scalar_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
790 for object in generated.object_types.values() {
791 for field in object.fields.iter().chain(&object.writable_fields) {
797 scalar_names.insert(leaf_scalar_name(&field.graphql_type));
798 }
799 }
800 scalar_names.insert("JSON".to_string());
803 for function in &generated.function_fields {
806 for (_, pg_type, _) in &function.arguments {
807 scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
808 }
809 }
810 for object in generated.object_types.values() {
813 for field in &object.fields {
814 for (_, pg_type, _) in &field.arguments {
815 scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
816 }
817 }
818 }
819 for relationships in generated.relationship_fields.values() {
820 for relationship in relationships {
821 for (_, pg_type, _) in &relationship.arguments {
822 scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
823 }
824 }
825 }
826
827 scalar_names.extend(bool_exp_scalars);
833
834 for name in scalar_names {
835 if matches!(name.as_str(), "Int" | "Float" | "String" | "Boolean" | "ID") {
836 continue;
837 }
838 if generated.enum_types.contains_key(&name) {
842 continue;
843 }
844 builder = builder
845 .register(Scalar::new(&name).description(format!("The PostgreSQL `{}` type.", name)));
846 }
847
848 let mut key_inputs: HashMap<String, InputObject> = HashMap::new();
856 for field in &generated.mutation_fields {
857 if field.mutation_type != MutationType::UpdateByPk || field.pk_columns.is_empty() {
858 continue;
859 }
860 let base_name = field.local_type_name.as_str();
861 let type_name = format!("{}_pk_columns_input", base_name);
862 let mut input = InputObject::new(&type_name)
863 .description(format!("The primary key of one {} row.", base_name));
864 for (column, pg_type) in &field.pk_columns {
865 input = input.field(InputValue::new(
866 names
867 .column(&field.schema_name, &field.table_name, column)
868 .unwrap_or(column),
869 TypeRef::named_nn(exposed_pk_argument_type(
870 names.as_ref(),
871 &field.schema_name,
872 &field.table_name,
873 column,
874 pg_type,
875 )),
876 ));
877 }
878 key_inputs.insert(type_name, input);
879 }
880 for (_, input) in key_inputs {
881 builder = builder.register(input);
882 }
883
884 let insertable_types: HashSet<&str> = generated
905 .object_types
906 .iter()
907 .filter(|(_, object)| crate::input::mutation::is_insertable(&object.table))
908 .map(|(type_name, _)| type_name.as_str())
909 .collect();
910
911 for (type_name, object) in &generated.object_types {
912 let local_type_name = object.local_name.as_str();
913 let table = &object.table;
914 let writable_named = |allowed: Option<&crate::names::ColumnSet>| {
922 object
923 .writable_fields
924 .iter()
925 .filter(|field| {
926 let column = table_column_for(&names, table, &field.name);
927 table.get_column(column).is_some()
928 && allowed.is_none_or(|set| set.allows(column))
929 })
930 .collect::<Vec<&crate::schema::object::GraphQLField>>()
931 };
932 let writable = writable_named(None);
933 let insert_fields = write_fields(object, &names, role, crate::role::Verb::Insert);
934 let update_fields = write_fields(object, &names, role, crate::role::Verb::Update);
935 if writable.is_empty() {
936 continue;
937 }
938
939 let relationships = generated
940 .relationship_fields
941 .get(type_name)
942 .map(|r| r.as_slice())
943 .unwrap_or(&[]);
944 let conflict_type = match table.unique_constraints.is_empty() {
945 true => None,
946 false => Some(format!("{}_on_conflict", local_type_name)),
947 };
948
949 if crate::input::mutation::is_insertable(table) {
950 let mut insert = InputObject::new(format!("{}_insert_input", local_type_name))
951 .description(format!("The columns of a new {} row.", local_type_name));
952 let mut taken: HashSet<&str> = HashSet::new();
953 for field in &insert_fields {
954 taken.insert(field.name.as_str());
958 insert = insert.field(InputValue::new(
959 &field.name,
960 write_type_ref(&field.graphql_type),
961 ));
962 }
963 for relationship in relationships {
967 if !insertable_types.contains(relationship.target_type.as_str()) {
968 continue;
969 }
970 if !taken.insert(relationship.name.as_str()) {
971 continue;
972 }
973 let suffix = match relationship.is_list {
974 true => "arr_rel_insert_input",
975 false => "obj_rel_insert_input",
976 };
977 insert = insert.field(InputValue::new(
978 &relationship.name,
979 TypeRef::named(format!("{}_{}", relationship.target_local_name, suffix)),
980 ));
981 }
982 builder = builder.register(insert);
983
984 let data_type = format!("{}_insert_input", local_type_name);
988 let mut object_rel =
989 InputObject::new(format!("{}_obj_rel_insert_input", local_type_name))
990 .description(format!(
991 "One {} row written beside its parent.",
992 local_type_name
993 ))
994 .field(InputValue::new("data", TypeRef::named_nn(&data_type)));
995 let mut array_rel =
996 InputObject::new(format!("{}_arr_rel_insert_input", local_type_name))
997 .description(format!(
998 "{} rows written beside their parent.",
999 local_type_name
1000 ))
1001 .field(InputValue::new(
1002 "data",
1003 TypeRef::named_nn_list_nn(&data_type),
1004 ));
1005 if let Some(conflict) = &conflict_type {
1006 object_rel =
1007 object_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1008 array_rel =
1009 array_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1010 }
1011 builder = builder.register(object_rel).register(array_rel);
1012 }
1013
1014 if crate::input::mutation::is_updatable(table) {
1015 let mut set = InputObject::new(format!("{}_set_input", local_type_name))
1016 .description(format!("Columns of {} to replace.", local_type_name));
1017 let mut numeric = InputObject::new(format!("{}_inc_input", local_type_name))
1018 .description(format!("Columns of {} to add to.", local_type_name));
1019 let mut any_numeric = false;
1020 let mut any_jsonb = false;
1021 for field in &update_fields {
1022 if matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1023 any_jsonb = true;
1024 }
1025 set = set.field(InputValue::new(
1026 &field.name,
1027 write_type_ref(&field.graphql_type),
1028 ));
1029 if crate::schema::aggregate::is_incrementable(&field.graphql_type) {
1032 any_numeric = true;
1033 numeric = numeric.field(InputValue::new(
1034 &field.name,
1035 TypeRef::named(leaf_scalar_name(&field.graphql_type)),
1036 ));
1037 }
1038 }
1039 builder = builder.register(set);
1040 if any_numeric {
1042 builder = builder.register(numeric);
1043 }
1044
1045 let mut updates = InputObject::new(format!("{}_updates", local_type_name))
1050 .description(format!(
1051 "One update to {}: which rows, and what to write.",
1052 local_type_name
1053 ))
1054 .field(InputValue::new(
1055 "where",
1056 TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1057 ))
1058 .field(InputValue::new(
1059 "_set",
1060 TypeRef::named(format!("{}_set_input", local_type_name)),
1061 ));
1062 if any_numeric {
1063 updates = updates.field(InputValue::new(
1064 "_inc",
1065 TypeRef::named(format!("{}_inc_input", local_type_name)),
1066 ));
1067 }
1068 for (operator, _) in JSONB_OPERATORS {
1075 if !any_jsonb {
1076 break;
1077 }
1078 updates = updates.field(InputValue::new(
1079 *operator,
1080 TypeRef::named(jsonb_operator_input(local_type_name, operator)),
1081 ));
1082 }
1083 builder = builder.register(updates);
1084 if any_jsonb {
1085 for (operator, item) in JSONB_OPERATORS {
1086 let mut input =
1087 InputObject::new(jsonb_operator_input(local_type_name, operator))
1088 .description(format!(
1089 "Columns of {} to apply `{}` to.",
1090 local_type_name, operator
1091 ));
1092 for field in &update_fields {
1093 if !matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1094 continue;
1095 }
1096 input = input.field(InputValue::new(&field.name, item()));
1097 }
1098 builder = builder.register(input);
1099 }
1100 }
1101 }
1102 }
1103
1104 for function in &generated.function_fields {
1107 if function.arguments.is_empty() {
1108 continue;
1109 }
1110 let mut args = InputObject::new(format!("{}_args", function.name))
1111 .description(format!("Arguments to {}.", function.name));
1112 for (name, pg_type, _) in &function.arguments {
1113 let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1114 args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1122 }
1123 builder = builder.register(args);
1124 }
1125
1126 for (type_name, relationships) in &generated.relationship_fields {
1131 let local_type_name = generated
1132 .object_types
1133 .get(type_name)
1134 .map(|object| object.local_name.as_str())
1135 .unwrap_or(type_name);
1136 for relationship in relationships {
1137 if relationship.arguments.is_empty() {
1138 continue;
1139 }
1140 let mut args =
1141 InputObject::new(computed_args_type_name(local_type_name, &relationship.name))
1142 .description(format!(
1143 "Arguments to {}, beside the row it is asked of.",
1144 relationship.name
1145 ));
1146 for (name, pg_type, _) in &relationship.arguments {
1147 let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1148 args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1150 }
1151 builder = builder.register(args);
1152 }
1153 }
1154
1155 for object in generated.object_types.values() {
1160 let local_type_name = object.local_name.as_str();
1161 for field in &object.fields {
1162 if field.arguments.is_empty() {
1163 continue;
1164 }
1165 let mut args = InputObject::new(computed_args_type_name(local_type_name, &field.name))
1166 .description(format!(
1167 "Arguments to {}, beside the row it is asked of.",
1168 field.name
1169 ));
1170 for (name, pg_type, _) in &field.arguments {
1171 let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1172 args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1174 }
1175 builder = builder.register(args);
1176 }
1177 }
1178
1179 {
1183 use crate::input::bool_exp as be;
1184 let mut targets: BTreeMap<&str, &str> = BTreeMap::new();
1185 for relationships in generated.relationship_fields.values() {
1186 for relationship in relationships {
1187 if relationship.is_list {
1188 targets.insert(
1189 relationship.target_local_name.as_str(),
1190 relationship.target_type.as_str(),
1191 );
1192 }
1193 }
1194 }
1195 for (target, target_type) in targets {
1196 let Some(object) = generated.object_types.get(target_type) else {
1197 continue;
1198 };
1199 let booleans: Vec<&str> = object
1203 .fields
1204 .iter()
1205 .filter(|field| {
1206 object.table.get_column(&field.name).is_some()
1207 && matches!(field.graphql_type, crate::types::GraphQLType::Boolean)
1208 })
1209 .map(|field| field.name.as_str())
1210 .collect();
1211
1212 let mut over = InputObject::new(be::aggregate_bool_exp_type_name(target)).description(
1213 format!("A question about the whole set of related {} rows.", target),
1214 );
1215 for function in be::AGGREGATE_PREDICATES {
1216 if function != "count" && booleans.is_empty() {
1217 continue;
1218 }
1219 over = over.field(InputValue::new(
1220 function,
1221 TypeRef::named(be::aggregate_bool_exp_function(target, function)),
1222 ));
1223
1224 let (arguments, predicate) = match function {
1227 "count" => (
1228 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1229 target,
1230 )),
1231 be::comparison_type_name("Int"),
1232 ),
1233 _ => (
1234 TypeRef::named_nn(be::aggregate_bool_exp_columns(target, function)),
1235 be::comparison_type_name("Boolean"),
1236 ),
1237 };
1238 let mut input = InputObject::new(be::aggregate_bool_exp_function(target, function))
1239 .description(format!(
1240 "`{}` over the related {} rows, and what it has to be.",
1241 function, target
1242 ))
1243 .field(InputValue::new("arguments", arguments))
1244 .field(InputValue::new("distinct", TypeRef::named("Boolean")))
1245 .field(InputValue::new(
1246 "filter",
1247 TypeRef::named(be::bool_exp_type_name(target)),
1248 ))
1249 .field(InputValue::new("predicate", TypeRef::named_nn(predicate)));
1250 if function == "count" {
1251 input = input.description(format!(
1252 "How many related {} rows there have to be.",
1253 target
1254 ));
1255 }
1256 builder = builder.register(input);
1257
1258 if function != "count" {
1259 let mut columns = Enum::new(be::aggregate_bool_exp_columns(target, function))
1260 .description(format!("A boolean column of {}.", target));
1261 for name in &booleans {
1262 columns = columns.item(EnumItem::new(*name));
1263 }
1264 builder = builder.register(columns);
1265 }
1266 }
1267 builder = builder.register(over);
1268 }
1269 }
1270
1271 for object in generated.object_types.values() {
1275 let local_type_name = object.local_name.as_str();
1276 let updatable_columns = write_fields(object, &names, role, crate::role::Verb::Update);
1282 if object.table.unique_constraints.is_empty() {
1283 continue;
1284 }
1285
1286 let mut constraints =
1287 Enum::new(format!("{}_constraint", local_type_name)).description(format!(
1288 "A uniqueness of {} that an insert may conflict with.",
1289 local_type_name
1290 ));
1291 for (name, columns) in &object.table.unique_constraints {
1292 constraints = constraints
1293 .item(EnumItem::new(name).description(format!("unique ({})", columns.join(", "))));
1294 }
1295
1296 let mut updatable = Enum::new(format!("{}_update_column", local_type_name)).description(
1297 format!("A column of {} an upsert may write.", local_type_name),
1298 );
1299 for field in &updatable_columns {
1300 updatable = updatable.item(EnumItem::new(&field.name));
1301 }
1302 if updatable_columns.is_empty() {
1311 updatable = updatable.item(
1312 EnumItem::new(PLACEHOLDER_COLUMN)
1313 .description("No column may be written by this upsert."),
1314 );
1315 }
1316
1317 let on_conflict = InputObject::new(format!("{}_on_conflict", local_type_name))
1318 .description(format!(
1319 "What to do when an insert into {} conflicts.",
1320 local_type_name
1321 ))
1322 .field(InputValue::new(
1323 "constraint",
1324 TypeRef::named_nn(format!("{}_constraint", local_type_name)),
1325 ))
1326 .field(
1331 InputValue::new(
1332 "update_columns",
1333 TypeRef::named_nn_list_nn(format!("{}_update_column", local_type_name)),
1334 )
1335 .default_value(Value::List(Vec::new())),
1336 )
1337 .field(InputValue::new(
1338 "where",
1339 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1340 ));
1341
1342 builder = builder
1343 .register(constraints)
1344 .register(updatable)
1345 .register(on_conflict);
1346 }
1347
1348 for (type_name, members) in &generated.enum_types {
1350 let mut generated_enum =
1351 Enum::new(type_name).description(format!("The values {} allows.", type_name));
1352 for (value, comment) in members {
1353 let item = EnumItem::new(value);
1354 generated_enum = generated_enum.item(match comment {
1355 Some(description) => item.description(description),
1356 None => item,
1357 });
1358 }
1359 builder = builder.register(generated_enum);
1360 }
1361
1362 let (order_inputs, order_enums) = crate::input::order_by::build_inputs(
1365 &generated.object_types,
1366 &generated.relationship_fields,
1367 );
1368 for input in order_inputs {
1369 builder = builder.register(input);
1370 }
1371 for enum_type in order_enums {
1372 builder = builder.register(enum_type);
1373 }
1374
1375 builder
1376 .finish()
1377 .map_err(|e| GraphQLError::SchemaError(e.to_string()))
1378}
1379
1380const PLACEHOLDER_COLUMN: &str = "_PLACEHOLDER";
1387
1388pub fn mutation_response_type_name(base_name: &str) -> String {
1390 format!("{}_mutation_response", base_name)
1391}
1392
1393fn create_mutation_response_type(base_name: &str, row_type: &str, returning: bool) -> Object {
1400 let response_name = mutation_response_type_name(base_name);
1401
1402 let response = Object::new(&response_name)
1403 .description(format!("The rows {} changed, and how many.", base_name))
1404 .field(Field::new(
1405 "affected_rows",
1406 TypeRef::named_nn(TypeRef::INT),
1407 |ctx| {
1408 FieldFuture::new(async move {
1409 if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1410 if let Some(value) = map.get(&async_graphql::Name::new("affected_rows")) {
1411 return Ok(Some(FieldValue::value(value.clone())));
1412 }
1413 }
1414 Ok(Some(FieldValue::value(Value::from(0))))
1415 })
1416 },
1417 ));
1418 if !returning {
1422 return response;
1423 }
1424 response.field(Field::new(
1425 "returning",
1426 TypeRef::named_nn_list_nn(row_type.to_string()),
1427 |ctx| {
1428 FieldFuture::new(async move {
1429 let rows = match ctx.parent_value.as_value() {
1430 Some(Value::Object(map)) => {
1431 match map.get(&async_graphql::Name::new("returning")) {
1432 Some(Value::List(items)) => items.clone(),
1433 _ => Vec::new(),
1434 }
1435 }
1436 _ => Vec::new(),
1437 };
1438 Ok(Some(FieldValue::list(
1439 rows.into_iter().map(FieldValue::value),
1440 )))
1441 })
1442 },
1443 ))
1444}
1445
1446fn create_aggregate_types(base_name: &str, object: &TableObjectType) -> Vec<Object> {
1452 use crate::schema::aggregate as agg;
1453
1454 let mut types = Vec::new();
1455
1456 let fields_type = agg::aggregate_fields_type_name(base_name);
1458 let has_rows = !object.fields.is_empty();
1462 let mut over = Object::new(agg::aggregate_type_name(base_name))
1463 .description(match has_rows {
1464 true => format!("Aggregates over {}, with the rows themselves.", base_name),
1465 false => format!("Aggregates over {}.", base_name),
1466 })
1467 .field(Field::new(
1468 "aggregate",
1469 TypeRef::named(&fields_type),
1470 |ctx| FieldFuture::new(async move { Ok(child_of(&ctx, "aggregate")) }),
1471 ));
1472 if has_rows {
1473 over = over.field(Field::new(
1474 "nodes",
1475 TypeRef::named_nn_list_nn(object.name.clone()),
1476 |ctx| {
1477 FieldFuture::new(async move {
1478 let rows = match child_value(&ctx, "nodes") {
1479 Some(Value::List(items)) => items,
1480 _ => Vec::new(),
1481 };
1482 Ok(Some(FieldValue::list(
1483 rows.into_iter().map(FieldValue::value),
1484 )))
1485 })
1486 },
1487 ));
1488 }
1489 types.push(over);
1490
1491 let mut count = Field::new("count", TypeRef::named_nn(TypeRef::INT), |ctx| {
1493 let key = ctx.ctx.field().alias().unwrap_or("count").to_string();
1498 FieldFuture::new(async move {
1499 Ok(Some(FieldValue::value(
1500 child_value(&ctx, &key).unwrap_or(Value::from(0)),
1501 )))
1502 })
1503 });
1504 if has_rows {
1510 count = count
1511 .argument(InputValue::new(
1512 "columns",
1513 TypeRef::named_nn_list(agg_select_column(base_name)),
1514 ))
1515 .argument(InputValue::new("distinct", TypeRef::named("Boolean")));
1516 }
1517 let mut aggregate_fields = Object::new(&fields_type)
1518 .description(format!("Aggregate functions over {}.", base_name))
1519 .field(count);
1520
1521 for (function, returns, columns) in agg::functions_for(object) {
1522 let function_type = agg::function_fields_type_name(base_name, function);
1523 let mut per_column = Object::new(&function_type).description(format!(
1524 "`{}` of each {} column it applies to.",
1525 function, base_name
1526 ));
1527 for column in &columns {
1528 let column_name = column.clone();
1529 let type_name = agg::field_type_for(object, column, returns);
1530 per_column =
1531 per_column.field(Field::new(column, TypeRef::named(type_name), move |ctx| {
1532 let column_name = column_name.clone();
1533 FieldFuture::new(async move {
1534 Ok(child_value(&ctx, &column_name).map(FieldValue::value))
1535 })
1536 }));
1537 }
1538 types.push(per_column);
1539
1540 let owned = function.to_string();
1541 aggregate_fields = aggregate_fields.field(Field::new(
1542 function,
1543 TypeRef::named(&function_type),
1544 move |ctx| {
1545 let owned = owned.clone();
1546 FieldFuture::new(async move { Ok(child_of(&ctx, &owned)) })
1547 },
1548 ));
1549 }
1550
1551 types.push(aggregate_fields);
1552 types
1553}
1554
1555fn agg_select_column(base_name: &str) -> String {
1557 crate::input::order_by::select_column_type_name(base_name)
1558}
1559
1560fn child_value(ctx: &ResolverContext<'_>, key: &str) -> Option<Value> {
1562 match ctx.parent_value.as_value() {
1563 Some(Value::Object(map)) => map.get(&async_graphql::Name::new(key)).cloned(),
1564 _ => None,
1565 }
1566}
1567
1568fn child_of<'a>(ctx: &ResolverContext<'_>, key: &str) -> Option<FieldValue<'a>> {
1573 match child_value(ctx, key) {
1574 None | Some(Value::Null) => None,
1575 Some(value) => Some(FieldValue::value(value)),
1576 }
1577}
1578
1579fn coded_error(code: &'static str, message: impl Into<String>) -> async_graphql::Error {
1585 let mut error = async_graphql::Error::new(message);
1586 let mut extensions = async_graphql::ErrorExtensionValues::default();
1587 extensions.set("code", code);
1588 error.extensions = Some(extensions);
1589 error
1590}
1591
1592fn validation_error(message: impl Into<String>) -> async_graphql::Error {
1594 coded_error("validation-failed", message)
1595}
1596
1597fn database_error(error: sqlx::Error) -> async_graphql::Error {
1609 let sqlx::Error::Database(db) = &error else {
1610 return async_graphql::Error::new(error.to_string());
1611 };
1612 let described = match db.code().as_deref() {
1613 Some("23505") => "Uniqueness violation. ",
1616 Some("23502") => "Not-NULL violation. ",
1617 Some("23503") => "Foreign key violation. ",
1618 Some("23514") => "Check constraint violation. ",
1619 _ => "",
1623 };
1624 let error = async_graphql::Error::new(format!("{}{}", described, db.message()));
1625 let coded = match db.code().as_deref() {
1630 Some(code) if code.starts_with("23") => Some("constraint-violation"),
1631 Some("22025") => Some("bad-request"),
1635 Some(code) if code.starts_with("22") => Some("data-exception"),
1637 _ => None,
1638 };
1639 match coded {
1640 Some(code) => at_code(error, code),
1641 None => error,
1642 }
1643}
1644
1645fn at_code(mut error: async_graphql::Error, code: &'static str) -> async_graphql::Error {
1647 let mut extensions = error.extensions.take().unwrap_or_default();
1648 extensions.set("code", code);
1649 error.extensions = Some(extensions);
1650 error
1651}
1652
1653fn at_path(mut error: async_graphql::Error, path: &str) -> async_graphql::Error {
1662 let mut extensions = error.extensions.take().unwrap_or_default();
1663 extensions.set("path", path);
1664 error.extensions = Some(extensions);
1665 error
1666}
1667
1668fn create_object_type(
1669 obj: &TableObjectType,
1670 relationships: &[RelationshipField],
1671 federation_entity: Option<&FederationEntity>,
1672) -> Object {
1673 let mut object = Object::new(&obj.name);
1674 let entity_is_shared = federation_entity.is_some_and(|entity| entity.shared);
1675 if let Some(entity) = federation_entity {
1676 object = object.key(entity.key_fields_sdl());
1677 }
1678
1679 object = match obj.description() {
1683 Some("") => object,
1686 Some(desc) => object.description(desc),
1687 None => object.description(format!(
1688 "columns and relationships of \"{}\"",
1689 obj.table_name()
1690 )),
1691 };
1692
1693 let mut taken: HashSet<String> = obj.fields.iter().map(|f| f.name.clone()).collect();
1706
1707 let mut fields: Vec<(String, Field)> = Vec::new();
1711
1712 for field in &obj.fields {
1713 let field_name = field.name.clone();
1714 let field_type = graphql_type_ref(&field.type_string());
1715 let takes_path = matches!(&field.graphql_type, crate::types::GraphQLType::Json)
1721 || matches!(
1722 &field.graphql_type,
1723 crate::types::GraphQLType::Custom(name) if name == "json"
1724 );
1725
1726 let gql_field = Field::new(&field.name, field_type, move |ctx| {
1730 let field_name = field_name.clone();
1731 FieldFuture::new(async move {
1732 if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1734 let key = async_graphql::Name::new(&field_name);
1736 let path = match takes_path {
1737 true => ctx.args.get("path").and_then(|v| v.string().ok()),
1738 false => None,
1739 };
1740 match map.get(&key) {
1741 Some(Value::Null) => return Ok(None),
1746 Some(val) => {
1747 let val = match path {
1748 Some(path) => walk_json_path(val, path)?,
1749 None => val.clone(),
1750 };
1751 return match val {
1752 Value::Null => Ok(None),
1753 val => Ok(Some(FieldValue::value(val))),
1754 };
1755 }
1756 None => {}
1757 }
1758 }
1759
1760 Ok(None)
1762 })
1763 });
1764
1765 let gql_field = match takes_path {
1766 true => gql_field.argument(
1767 InputValue::new("path", TypeRef::named("String")).description("JSON select path"),
1768 ),
1769 false => gql_field,
1770 };
1771
1772 let gql_field = match field.arguments.is_empty() {
1776 true => gql_field,
1777 false => gql_field.argument(InputValue::new(
1778 "args",
1779 TypeRef::named_nn(computed_args_type_name(&obj.local_name, &field.name)),
1780 )),
1781 };
1782
1783 let gql_field = if let Some(desc) = &field.description {
1784 gql_field.description(desc)
1785 } else {
1786 gql_field
1787 };
1788 let gql_field = if entity_is_shared && !field.is_pk {
1789 gql_field.shareable()
1790 } else {
1791 gql_field
1792 };
1793
1794 fields.push((field.name.clone(), gql_field));
1795 }
1796
1797 for rel in relationships {
1801 if !taken.insert(rel.name.clone()) {
1802 tracing::warn!(
1803 "{}: not exposing the relationship to \"{}\" as `{}` -- the table \
1804 already has a column of that name",
1805 obj.name,
1806 rel.target_type,
1807 rel.name
1808 );
1809 continue;
1810 }
1811 let field_name = rel.name.clone();
1812 let field_type = if rel.is_list {
1813 TypeRef::named_nn_list_nn(&rel.target_type)
1814 } else if always_present(rel, &obj.table) {
1815 TypeRef::named_nn(&rel.target_type)
1816 } else {
1817 TypeRef::named(&rel.target_type)
1818 };
1819
1820 let is_list = rel.is_list;
1821 let mut gql_field = Field::new(&rel.name, field_type, move |ctx| {
1822 let field_name = field_name.clone();
1823 FieldFuture::new(async move {
1824 if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1825 let key = async_graphql::Name::new(&field_name);
1826 match map.get(&key) {
1827 Some(Value::Null) if !is_list => return Ok(None),
1832 Some(val) => return Ok(Some(FieldValue::value(val.clone()))),
1833 None => {}
1834 }
1835 }
1836 Ok(None)
1837 })
1838 });
1839
1840 if !rel.arguments.is_empty() {
1851 gql_field = gql_field.argument(InputValue::new(
1852 "args",
1853 TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1854 ));
1855 }
1856 if rel.is_list {
1857 gql_field = gql_field
1858 .argument(InputValue::new(
1859 "distinct_on",
1860 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1861 &rel.target_local_name,
1862 )),
1863 ))
1864 .argument(InputValue::new(
1865 "where",
1866 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1867 &rel.target_local_name,
1868 )),
1869 ))
1870 .argument(InputValue::new(
1871 "order_by",
1872 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1873 &rel.target_local_name,
1874 )),
1875 ))
1876 .argument(InputValue::new("limit", TypeRef::named("Int")))
1877 .argument(InputValue::new("offset", TypeRef::named("Int")));
1878 }
1879
1880 let gql_field = if let Some(desc) = &rel.description {
1881 gql_field.description(desc)
1882 } else {
1883 gql_field
1884 };
1885 let gql_field = if entity_is_shared {
1886 gql_field.shareable()
1887 } else {
1888 gql_field
1889 };
1890
1891 fields.push((rel.name.clone(), gql_field));
1892
1893 if rel.is_list {
1898 let aggregate_field = format!("{}_aggregate", rel.name);
1899 if taken.insert(aggregate_field.clone()) {
1900 let key = aggregate_field.clone();
1901 fields.push((
1902 aggregate_field.clone(),
1903 Field::new(
1904 &aggregate_field,
1905 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
1906 &rel.target_local_name,
1907 )),
1908 move |ctx| {
1909 let key = key.clone();
1910 FieldFuture::new(async move {
1911 Ok(Some(FieldValue::value(
1915 child_value(&ctx, &key).unwrap_or(Value::Null),
1916 )))
1917 })
1918 },
1919 )
1920 .argument(InputValue::new(
1925 "distinct_on",
1926 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1927 &rel.target_local_name,
1928 )),
1929 ))
1930 .argument(InputValue::new(
1931 "where",
1932 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1933 &rel.target_local_name,
1934 )),
1935 ))
1936 .argument(InputValue::new(
1937 "order_by",
1938 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1939 &rel.target_local_name,
1940 )),
1941 ))
1942 .argument(InputValue::new("limit", TypeRef::named("Int")))
1943 .argument(InputValue::new("offset", TypeRef::named("Int")))
1944 .description(format!("Aggregates over {}.", rel.name)),
1945 ));
1946 if !rel.arguments.is_empty() {
1949 let last = fields.len() - 1;
1950 let (name, field) = fields.remove(last);
1951 fields.push((
1952 name,
1953 field.argument(InputValue::new(
1954 "args",
1955 TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1956 )),
1957 ));
1958 }
1959 if entity_is_shared {
1960 let last = fields.len() - 1;
1961 let (name, field) = fields.remove(last);
1962 fields.push((name, field.shareable()));
1963 }
1964 }
1965 }
1966 }
1967
1968 fields.sort_by(|(a, _), (b, _)| a.cmp(b));
1969 for (_, field) in fields {
1970 object = object.field(field);
1971 }
1972
1973 object
1974}
1975
1976trait RootField: Sized {
1983 fn with_argument(self, value: InputValue) -> Self;
1984}
1985
1986impl RootField for Field {
1987 fn with_argument(self, value: InputValue) -> Self {
1988 self.argument(value)
1989 }
1990}
1991
1992impl RootField for SubscriptionField {
1993 fn with_argument(self, value: InputValue) -> Self {
1994 self.argument(value)
1995 }
1996}
1997
1998fn with_row_arguments<F: RootField>(field: F, type_name: &str) -> F {
2001 with_row_arguments_named(field, type_name, true)
2002}
2003
2004fn with_row_arguments_named<F: RootField>(field: F, type_name: &str, columns: bool) -> F {
2012 let mut field = field;
2013 if columns {
2014 field = field.with_argument(InputValue::new(
2015 "distinct_on",
2016 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(type_name)),
2017 ));
2018 }
2019 field = field
2020 .with_argument(InputValue::new("limit", TypeRef::named("Int")))
2021 .with_argument(InputValue::new("offset", TypeRef::named("Int")));
2022 if columns {
2023 field = field.with_argument(InputValue::new(
2024 "order_by",
2025 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(type_name)),
2026 ));
2027 }
2028 field.with_argument(InputValue::new(
2029 "where",
2030 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(type_name)),
2031 ))
2032}
2033
2034fn with_key_arguments<F: RootField>(
2037 field: F,
2038 schema_name: &str,
2039 table_name: &str,
2040 pk_columns: &[(String, String)],
2041 names: &crate::names::NameOverrides,
2042) -> F {
2043 let mut field = field;
2044 for (column, pg_type) in pk_columns {
2045 field = field.with_argument(InputValue::new(
2046 names
2047 .column(schema_name, table_name, column)
2048 .unwrap_or(column),
2049 TypeRef::named_nn(exposed_pk_argument_type(
2050 names,
2051 schema_name,
2052 table_name,
2053 column,
2054 pg_type,
2055 )),
2056 ));
2057 }
2058 field
2059}
2060
2061fn create_query_type(
2063 generated: &GeneratedSchema,
2064 root_name: &str,
2065 max_rows: Option<i64>,
2066 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2067 names: Arc<crate::names::NameOverrides>,
2068) -> Object {
2069 let mut roots: Vec<(String, Field)> = Vec::new();
2074
2075 for field in &generated.query_fields {
2076 let table_name = field.table_name.clone();
2077 let schema_name = field.schema_name.clone();
2078 let type_name = field.type_name.clone();
2079 let local_type_name = field.local_type_name.clone();
2080 let is_by_pk = field.is_by_pk;
2081 let pk_columns = field.pk_columns.clone();
2082 let return_type = graphql_type_ref(&field.return_type);
2083
2084 let spec_type_name = local_type_name.clone();
2085 let spec = Arc::new(QueryFieldSpec {
2086 schema_name,
2087 table_name,
2088 type_name,
2089 is_by_pk,
2090 pk_columns: pk_columns.clone(),
2091 max_rows,
2092 relationships: Arc::clone(&relationships),
2093 names: Arc::clone(&names),
2094 call: None,
2095 });
2096
2097 let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2098 let spec = Arc::clone(&spec);
2099 FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
2100 });
2101
2102 gql_field = match is_by_pk {
2104 false => with_row_arguments(gql_field, &spec_type_name),
2105 true => with_key_arguments(
2106 gql_field,
2107 &field.schema_name,
2108 &field.table_name,
2109 &pk_columns,
2110 names.as_ref(),
2111 ),
2112 };
2113
2114 if let Some(desc) = &field.description {
2115 gql_field = gql_field.description(desc);
2116 }
2117
2118 if field.rows {
2123 roots.push((field.name.clone(), gql_field));
2124 }
2125
2126 if !is_by_pk && field.aggregates {
2134 let agg_spec = Arc::new(AggregateSpec {
2135 schema_name: field.schema_name.clone(),
2136 table_name: field.table_name.clone(),
2137 type_name: field.type_name.clone(),
2138 max_rows,
2139 relationships: Arc::clone(&relationships),
2140 names: Arc::clone(&names),
2141 call: None,
2142 });
2143 let aggregate_field_name = field.aggregate_name.clone().unwrap_or_else(|| {
2144 crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2145 });
2146 let aggregate_field_name_for_sorting = aggregate_field_name.clone();
2147 let mut agg_field = Field::new(
2148 aggregate_field_name,
2149 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2150 &field.local_type_name,
2151 )),
2152 move |ctx| {
2153 let agg_spec = Arc::clone(&agg_spec);
2154 FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
2155 },
2156 );
2157 agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2158 agg_field = match field.aggregate_description.as_deref() {
2159 Some("") => agg_field,
2160 Some(given) => agg_field.description(given),
2161 None => agg_field.description(format!(
2162 "fetch aggregated fields from the table: \"{}\"",
2163 field.table_name
2164 )),
2165 };
2166 roots.push((aggregate_field_name_for_sorting, agg_field));
2167 }
2168 }
2169
2170 roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2171 let mut query = Object::new(root_name);
2172 if roots.is_empty() {
2176 return query.field(
2177 Field::new("no_queries_available", TypeRef::named("String"), |_| {
2178 FieldFuture::new(async move { Ok(None::<Value>) })
2179 })
2180 .description("There are no queries available to the current role."),
2181 );
2182 }
2183 for (_, field) in roots {
2184 query = query.field(field);
2185 }
2186
2187 query
2188}
2189
2190type JsonbOperator = (&'static str, fn() -> TypeRef);
2192
2193const JSONB_OPERATORS: &[JsonbOperator] = &[
2200 ("_append", || TypeRef::named("jsonb")),
2201 ("_delete_at_path", || TypeRef::named_nn_list("String")),
2202 ("_delete_elem", || TypeRef::named("Int")),
2203 ("_delete_key", || TypeRef::named("String")),
2204 ("_prepend", || TypeRef::named("jsonb")),
2205];
2206
2207fn jsonb_operator_input(type_name: &str, operator: &str) -> String {
2209 format!("{}{}_input", type_name, operator)
2210}
2211
2212fn with_update_operators(
2219 field: Field,
2220 base_name: &str,
2221 has_numeric: bool,
2222 has_jsonb: bool,
2223) -> Field {
2224 let mut field = field;
2225 if has_jsonb {
2228 for (operator, _) in JSONB_OPERATORS {
2229 field = field.argument(InputValue::new(
2230 *operator,
2231 TypeRef::named(jsonb_operator_input(base_name, operator)),
2232 ));
2233 }
2234 }
2235 if has_numeric {
2236 field = field.argument(InputValue::new(
2237 "_inc",
2238 TypeRef::named(format!("{}_inc_input", base_name)),
2239 ));
2240 }
2241 field.argument(InputValue::new(
2242 "_set",
2243 TypeRef::named(format!("{}_set_input", base_name)),
2244 ))
2245}
2246
2247fn write_fields<'a>(
2257 object: &'a crate::schema::object::TableObjectType,
2258 names: &crate::names::NameOverrides,
2259 role: Option<&str>,
2260 verb: crate::role::Verb,
2261) -> Vec<&'a crate::schema::object::GraphQLField> {
2262 let allowed = role.and_then(|role| {
2263 names
2264 .permissions(&object.table.schema, &object.table.name, role)
2265 .and_then(|granted| granted.write_columns(verb))
2266 });
2267 object
2268 .writable_fields
2269 .iter()
2270 .filter(|field| {
2271 let column = table_column_for(names, &object.table, &field.name);
2272 object
2280 .table
2281 .get_column(column)
2282 .is_some_and(|c| !c.always_generated)
2283 && allowed.is_none_or(|set| set.allows(column))
2284 })
2285 .collect()
2286}
2287
2288fn update_inputs(
2290 object: &crate::schema::object::TableObjectType,
2291 names: &crate::names::NameOverrides,
2292 role: Option<&str>,
2293) -> (bool, bool) {
2294 let fields = write_fields(object, names, role, crate::role::Verb::Update);
2295 (
2296 fields
2297 .iter()
2298 .any(|f| crate::schema::aggregate::is_incrementable(&f.graphql_type)),
2299 fields
2300 .iter()
2301 .any(|f| matches!(&f.graphql_type, crate::types::GraphQLType::Json)),
2302 )
2303}
2304
2305fn create_mutation_type(
2306 generated: &GeneratedSchema,
2307 root_name: &str,
2308 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2309 type_names: Arc<HashMap<(String, String), String>>,
2310 names: Arc<crate::names::NameOverrides>,
2311 max_rows: Option<i64>,
2312 role: Option<&str>,
2313) -> Object {
2314 let mut mutation = Object::new(root_name);
2315 let mut roots: Vec<(String, Field)> = Vec::new();
2317
2318 let mut has_numeric_column: HashSet<String> = HashSet::new();
2325 let mut has_jsonb_column: HashSet<String> = HashSet::new();
2326 for object in generated.object_types.values() {
2327 let (numeric, jsonb) = update_inputs(object, &names, role);
2328 if numeric {
2329 has_numeric_column.insert(object.local_name.clone());
2330 }
2331 if jsonb {
2332 has_jsonb_column.insert(object.local_name.clone());
2333 }
2334 }
2335
2336 let has_conflict_target: HashSet<String> = generated
2337 .object_types
2338 .iter()
2339 .filter(|(_, object)| !object.table.unique_constraints.is_empty())
2340 .map(|(_, object)| object.local_name.clone())
2341 .collect();
2342
2343 for field in &generated.mutation_fields {
2344 let table_name = field.table_name.clone();
2345 let schema_name = field.schema_name.clone();
2346 let mutation_type = field.mutation_type;
2347 let pk_columns = field.pk_columns.clone();
2348 let return_type = graphql_type_ref(&field.return_type);
2349
2350 let where_type = field.local_type_name.clone();
2354
2355 let resolver_pk_columns = pk_columns.clone();
2356 let field_relationships = Arc::clone(&relationships);
2357 let field_type_names = Arc::clone(&type_names);
2358 let field_names = Arc::clone(&names);
2359 let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2360 let table_name = table_name.clone();
2361 let schema_name = schema_name.clone();
2362 let pk_columns = resolver_pk_columns.clone();
2363 let relationships = Arc::clone(&field_relationships);
2364 let type_names = Arc::clone(&field_type_names);
2365 let names = Arc::clone(&field_names);
2366 FieldFuture::new(async move {
2367 resolve_mutation(
2368 &ctx,
2369 &schema_name,
2370 &table_name,
2371 mutation_type,
2372 &pk_columns,
2373 &relationships,
2374 &type_names,
2375 &names,
2376 max_rows,
2377 )
2378 .await
2379 })
2380 });
2381
2382 match mutation_type {
2388 MutationType::Insert | MutationType::InsertOne => {
2389 let insert_input = format!("{}_insert_input", where_type);
2390 gql_field = if mutation_type == MutationType::Insert {
2391 gql_field.argument(InputValue::new(
2392 "objects",
2393 TypeRef::named_nn_list_nn(&insert_input),
2394 ))
2395 } else {
2396 gql_field.argument(InputValue::new("object", TypeRef::named_nn(&insert_input)))
2399 };
2400 if has_conflict_target.contains(&where_type) {
2401 gql_field = gql_field.argument(InputValue::new(
2402 "on_conflict",
2403 TypeRef::named(format!("{}_on_conflict", where_type)),
2404 ));
2405 }
2406 }
2407 MutationType::UpdateByPk => {
2408 gql_field = gql_field.argument(InputValue::new(
2409 "pk_columns",
2410 TypeRef::named_nn(format!("{}_pk_columns_input", where_type)),
2411 ));
2412 gql_field = with_update_operators(
2413 gql_field,
2414 &where_type,
2415 has_numeric_column.contains(&where_type),
2416 has_jsonb_column.contains(&where_type),
2417 );
2418 }
2419 MutationType::Update => {
2420 gql_field = gql_field.argument(InputValue::new(
2421 "where",
2422 TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2423 ));
2424 gql_field = with_update_operators(
2425 gql_field,
2426 &where_type,
2427 has_numeric_column.contains(&where_type),
2428 has_jsonb_column.contains(&where_type),
2429 );
2430 }
2431 MutationType::UpdateMany => {
2432 gql_field = gql_field.argument(InputValue::new(
2433 "updates",
2434 TypeRef::named_nn_list_nn(format!("{}_updates", where_type)),
2435 ));
2436 }
2437 MutationType::DeleteByPk => {
2438 for (col_name, pg_type) in &pk_columns {
2439 gql_field = gql_field.argument(InputValue::new(
2440 names
2441 .column(&field.schema_name, &field.table_name, col_name)
2442 .unwrap_or(col_name),
2443 TypeRef::named_nn(exposed_pk_argument_type(
2444 names.as_ref(),
2445 &field.schema_name,
2446 &field.table_name,
2447 col_name,
2448 pg_type,
2449 )),
2450 ));
2451 }
2452 }
2453 MutationType::Delete => {
2454 gql_field = gql_field.argument(InputValue::new(
2461 "where",
2462 TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2463 ));
2464 }
2465 }
2466
2467 if let Some(desc) = &field.description {
2468 gql_field = gql_field.description(desc);
2469 }
2470
2471 roots.push((field.name.clone(), gql_field));
2472 }
2473
2474 roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2475 for (_, field) in roots {
2476 mutation = mutation.field(field);
2477 }
2478
2479 mutation
2480}
2481
2482fn create_subscription_type(
2499 generated: &GeneratedSchema,
2500 root_name: &str,
2501 max_rows: Option<i64>,
2502 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2503 names: Arc<crate::names::NameOverrides>,
2504 refresh: std::time::Duration,
2505) -> Subscription {
2506 let mut roots: Vec<(String, SubscriptionField)> = Vec::new();
2507
2508 for field in &generated.query_fields {
2509 let spec_type_name = field.local_type_name.clone();
2510 let pk_columns = field.pk_columns.clone();
2511 let is_by_pk = field.is_by_pk;
2512 let return_type = graphql_type_ref(&field.return_type);
2513 let watched = vec![crate::subscription::table_channel_name(
2514 &field.schema_name,
2515 &field.table_name,
2516 )];
2517
2518 let spec = Arc::new(QueryFieldSpec {
2519 schema_name: field.schema_name.clone(),
2520 table_name: field.table_name.clone(),
2521 type_name: field.type_name.clone(),
2522 is_by_pk,
2523 pk_columns: pk_columns.clone(),
2524 max_rows,
2525 relationships: Arc::clone(&relationships),
2526 names: Arc::clone(&names),
2527 call: None,
2528 });
2529
2530 let mut gql_field = SubscriptionField::new(&field.name, return_type, move |ctx| {
2531 let spec = Arc::clone(&spec);
2532 let watched = watched.clone();
2533 SubscriptionFieldFuture::new(async move {
2534 let wake = wake_on(&ctx, &watched, refresh).await;
2535 Ok(live(ctx, LiveSource::Rows(spec), wake))
2536 })
2537 });
2538
2539 gql_field = match is_by_pk {
2540 false => with_row_arguments(gql_field, &spec_type_name),
2541 true => with_key_arguments(
2542 gql_field,
2543 &field.schema_name,
2544 &field.table_name,
2545 &pk_columns,
2546 names.as_ref(),
2547 ),
2548 };
2549 if let Some(desc) = &field.description {
2550 gql_field = gql_field.description(desc);
2551 }
2552 if field.rows {
2554 roots.push((field.name.clone(), gql_field));
2555 }
2556
2557 if is_by_pk || !field.aggregates {
2560 continue;
2561 }
2562
2563 let agg_spec = Arc::new(AggregateSpec {
2565 schema_name: field.schema_name.clone(),
2566 table_name: field.table_name.clone(),
2567 type_name: field.type_name.clone(),
2568 max_rows,
2569 relationships: Arc::clone(&relationships),
2570 names: Arc::clone(&names),
2571 call: None,
2572 });
2573 let agg_watched = vec![crate::subscription::table_channel_name(
2574 &field.schema_name,
2575 &field.table_name,
2576 )];
2577 let agg_name = field.aggregate_name.clone().unwrap_or_else(|| {
2578 crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2579 });
2580 let mut agg_field = SubscriptionField::new(
2581 agg_name.clone(),
2582 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2583 &field.local_type_name,
2584 )),
2585 move |ctx| {
2586 let agg_spec = Arc::clone(&agg_spec);
2587 let watched = agg_watched.clone();
2588 SubscriptionFieldFuture::new(async move {
2589 let wake = wake_on(&ctx, &watched, refresh).await;
2590 Ok(live(ctx, LiveSource::Aggregate(agg_spec), wake))
2591 })
2592 },
2593 );
2594 agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2595 agg_field = match field.aggregate_description.as_deref() {
2596 Some("") => agg_field,
2597 Some(given) => agg_field.description(given),
2598 None => agg_field.description(format!(
2599 "fetch aggregated fields from the table: \"{}\"",
2600 field.table_name
2601 )),
2602 };
2603 roots.push((agg_name, agg_field));
2604 }
2605
2606 roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2607 let mut subscription = Subscription::new(root_name);
2608 if roots.is_empty() {
2611 return subscription.field(SubscriptionField::new(
2612 "no_subscriptions_available",
2613 TypeRef::named("String"),
2614 |_| {
2615 SubscriptionFieldFuture::new(async move {
2616 Ok(futures::stream::empty::<
2617 Result<FieldValue, async_graphql::Error>,
2618 >())
2619 })
2620 },
2621 ));
2622 }
2623 for (_, field) in roots {
2624 subscription = subscription.field(field);
2625 }
2626 subscription
2627}
2628
2629async fn wake_on(
2635 ctx: &ResolverContext<'_>,
2636 channels: &[String],
2637 refresh: std::time::Duration,
2638) -> futures::stream::BoxStream<'static, ()> {
2639 use futures::StreamExt;
2640
2641 let mut streams: Vec<futures::stream::BoxStream<'static, ()>> =
2642 Vec::with_capacity(channels.len() + 1);
2643 streams.push(
2646 tokio_stream::wrappers::IntervalStream::new({
2647 let mut interval = tokio::time::interval(refresh);
2648 interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
2649 interval
2650 })
2651 .map(|_| ())
2652 .boxed(),
2653 );
2654
2655 if let Ok(broker_arc) = ctx.data::<Arc<RwLock<Option<NotifyBroker>>>>() {
2656 let guard = broker_arc.read().await;
2657 if let Some(broker) = guard.as_ref() {
2658 for channel in channels {
2659 streams.push(
2663 broker
2664 .subscribe_or_create(channel)
2665 .await
2666 .map(|_| ())
2667 .boxed(),
2668 );
2669 }
2670 }
2671 }
2672
2673 futures::stream::select_all(streams).boxed()
2674}
2675
2676enum LiveSource {
2682 Rows(Arc<QueryFieldSpec>),
2683 Aggregate(Arc<AggregateSpec>),
2684}
2685
2686async fn read_live<'a>(
2688 ctx: &ResolverContext<'a>,
2689 source: &LiveSource,
2690) -> Result<(serde_json::Value, Option<FieldValue<'a>>), async_graphql::Error> {
2691 match source {
2692 LiveSource::Rows(spec) => {
2693 let rows = query_rows(ctx, spec).await?;
2694 Ok((
2695 serde_json::Value::Array(rows.clone()),
2696 rows_as_field_value(rows, spec.is_by_pk),
2697 ))
2698 }
2699 LiveSource::Aggregate(spec) => {
2700 let value = aggregate_value(ctx, spec).await?;
2701 let seen = serde_json::to_value(&value).unwrap_or_default();
2702 Ok((seen, Some(FieldValue::value(value))))
2703 }
2704 }
2705}
2706
2707fn live<'a>(
2715 ctx: ResolverContext<'a>,
2716 source: LiveSource,
2717 wake: futures::stream::BoxStream<'static, ()>,
2718) -> impl futures::Stream<Item = Result<FieldValue<'a>, async_graphql::Error>> + Send + 'a {
2719 use futures::StreamExt;
2720
2721 struct Live<'a> {
2722 ctx: ResolverContext<'a>,
2723 source: LiveSource,
2724 wake: futures::stream::BoxStream<'static, ()>,
2725 sent: Option<serde_json::Value>,
2726 started: bool,
2727 failed: bool,
2729 }
2730
2731 futures::stream::unfold(
2732 Live {
2733 ctx,
2734 source,
2735 wake,
2736 sent: None,
2737 started: false,
2738 failed: false,
2739 },
2740 |mut live: Live<'a>| async move {
2741 if live.failed {
2742 return None;
2743 }
2744 loop {
2745 if live.started {
2746 live.wake.next().await?;
2747 } else {
2748 live.started = true;
2749 }
2750 let read = read_live(&live.ctx, &live.source).await;
2751 match read {
2752 Err(e) => {
2760 live.failed = true;
2761 return Some((Err(e), live));
2762 }
2763 Ok((seen, value)) => {
2764 if live.sent.as_ref() == Some(&seen) {
2765 continue;
2766 }
2767 live.sent = Some(seen);
2768 let value = value.unwrap_or_else(|| FieldValue::value(Value::Null));
2769 return Some((Ok(value), live));
2770 }
2771 }
2772 }
2773 },
2774 )
2775}
2776
2777struct QueryFieldSpec {
2779 schema_name: String,
2780 table_name: String,
2781 type_name: String,
2782 is_by_pk: bool,
2783 pk_columns: Vec<(String, String)>,
2784 max_rows: Option<i64>,
2785 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2786 names: Arc<crate::names::NameOverrides>,
2787 call: Option<Arc<FunctionCall>>,
2790}
2791
2792#[derive(Clone)]
2794struct EntityLookup {
2795 schema_name: String,
2796 table_name: String,
2797 type_name: String,
2798 key_columns: Vec<EntityKeyColumn>,
2799 row_column_types: HashMap<String, String>,
2800}
2801
2802#[derive(Clone)]
2803struct EntityKeyColumn {
2804 field_name: String,
2805 column_name: String,
2806 pg_type: String,
2807 explicit_id: bool,
2808}
2809
2810struct EntityResolverState {
2812 entities: HashMap<String, EntityLookup>,
2813 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2814 names: Arc<crate::names::NameOverrides>,
2815 max_rows: Option<i64>,
2816}
2817
2818impl EntityResolverState {
2819 fn new(
2820 generated: &GeneratedSchema,
2821 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2822 names: Arc<crate::names::NameOverrides>,
2823 max_rows: Option<i64>,
2824 ) -> Self {
2825 let mut entities = HashMap::new();
2826 for (type_name, entity) in &generated.federation_entities {
2827 let Some(object) = generated.object_types.get(type_name) else {
2828 continue;
2829 };
2830 let mut key_columns = Vec::with_capacity(entity.key_fields.len());
2831 for field_name in &entity.key_fields {
2832 let column_name = names
2833 .column_source(&object.table.schema, &object.table.name, field_name)
2834 .unwrap_or(field_name);
2835 let Some(column) = object.table.get_column(column_name) else {
2836 continue;
2837 };
2838 key_columns.push(EntityKeyColumn {
2839 field_name: field_name.clone(),
2840 column_name: column.name.clone(),
2841 pg_type: column.nominal_type.clone(),
2842 explicit_id: matches!(
2843 names.column_type(&object.table.schema, &object.table.name, &column.name),
2844 Some(crate::types::GraphQLType::Id)
2845 ),
2846 });
2847 }
2848 if key_columns.len() != entity.key_fields.len() {
2849 continue;
2850 }
2851 entities.insert(
2852 type_name.clone(),
2853 EntityLookup {
2854 schema_name: object.table.schema.clone(),
2855 table_name: object.table.name.clone(),
2856 type_name: type_name.clone(),
2857 key_columns,
2858 row_column_types: exposed_column_types(&object.table, names.as_ref()),
2859 },
2860 );
2861 }
2862 Self {
2863 entities,
2864 relationships,
2865 names,
2866 max_rows,
2867 }
2868 }
2869}
2870
2871struct EntityRepresentation {
2872 index: usize,
2873 type_name: String,
2874 values: serde_json::Map<String, serde_json::Value>,
2875}
2876
2877fn entity_field_applicability(
2884 selection_set: &SelectionSet,
2885 fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2886 type_name: &str,
2887) -> Vec<bool> {
2888 fn condition_matches(condition: Option<&Positioned<TypeCondition>>, type_name: &str) -> bool {
2889 condition.is_none_or(|condition| {
2890 let condition = condition.node.on.node.as_str();
2891 condition == type_name || condition == "_Entity"
2892 })
2893 }
2894
2895 fn collect(
2896 selection_set: &SelectionSet,
2897 fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2898 type_name: &str,
2899 parent_matches: bool,
2900 applicability: &mut Vec<bool>,
2901 ) {
2902 for selection in &selection_set.items {
2903 match &selection.node {
2904 Selection::Field(_) => applicability.push(parent_matches),
2905 Selection::FragmentSpread(spread) => {
2906 let Some(fragment) = fragments.get(&spread.node.fragment_name.node) else {
2907 continue;
2908 };
2909 collect(
2910 &fragment.node.selection_set.node,
2911 fragments,
2912 type_name,
2913 parent_matches
2914 && condition_matches(Some(&fragment.node.type_condition), type_name),
2915 applicability,
2916 );
2917 }
2918 Selection::InlineFragment(fragment) => collect(
2919 &fragment.node.selection_set.node,
2920 fragments,
2921 type_name,
2922 parent_matches
2923 && condition_matches(fragment.node.type_condition.as_ref(), type_name),
2924 applicability,
2925 ),
2926 }
2927 }
2928 }
2929
2930 let mut applicability = Vec::new();
2931 collect(
2932 selection_set,
2933 fragments,
2934 type_name,
2935 true,
2936 &mut applicability,
2937 );
2938 applicability
2939}
2940
2941fn entity_selection_fields<'ctx, 'a>(
2942 ctx: &'ctx ResolverContext<'a>,
2943 type_name: &str,
2944) -> Vec<SelectionField<'ctx>> {
2945 let applicability = entity_field_applicability(
2946 &ctx.ctx.item.node.selection_set.node,
2947 &ctx.ctx.query_env.fragments,
2948 type_name,
2949 );
2950 let fields: Vec<_> = ctx.field().selection_set().collect();
2951 debug_assert_eq!(fields.len(), applicability.len());
2952 fields
2953 .into_iter()
2954 .zip(applicability)
2955 .filter_map(|(field, applies)| applies.then_some(field))
2956 .collect()
2957}
2958
2959async fn resolve_entities<'a>(
2960 ctx: &ResolverContext<'a>,
2961 state: &EntityResolverState,
2962) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
2963 let representations = ctx.args.try_get("representations")?.list()?;
2964 let mut grouped: HashMap<String, Vec<EntityRepresentation>> = HashMap::new();
2965 let mut count = 0;
2966 for (index, representation) in representations.iter().enumerate() {
2967 count += 1;
2968 let value = accessor_to_json(&representation);
2969 let serde_json::Value::Object(values) = value else {
2970 return Err(async_graphql::Error::new(
2971 "entity representation must be an object",
2972 ));
2973 };
2974 let type_name = values
2975 .get("__typename")
2976 .and_then(serde_json::Value::as_str)
2977 .ok_or_else(|| {
2978 async_graphql::Error::new("entity representation is missing __typename")
2979 })?
2980 .to_string();
2981 grouped
2982 .entry(type_name.clone())
2983 .or_default()
2984 .push(EntityRepresentation {
2985 index,
2986 type_name,
2987 values,
2988 });
2989 }
2990
2991 let mut resolved: Vec<Option<FieldValue<'a>>> =
2992 std::iter::repeat_with(|| None).take(count).collect();
2993 for (type_name, representations) in grouped {
2994 let lookup = state.entities.get(&type_name).ok_or_else(|| {
2995 async_graphql::Error::new(format!("unknown federation entity \"{}\"", type_name))
2996 })?;
2997 let rows = resolve_entity_group(ctx, lookup, &representations, state).await?;
2998 for (ordinality, representation) in representations.into_iter().enumerate() {
2999 resolved[representation.index] = Some(match &rows[ordinality] {
3000 Some(row) => FieldValue::value(json_to_value(row.clone()))
3001 .with_type(representation.type_name),
3002 None => FieldValue::NULL,
3003 });
3004 }
3005 }
3006
3007 Ok(Some(FieldValue::list(resolved.into_iter().map(|value| {
3008 value.expect("every representation was resolved or errored")
3009 }))))
3010}
3011
3012async fn resolve_entity_group(
3013 ctx: &ResolverContext<'_>,
3014 lookup: &EntityLookup,
3015 representations: &[EntityRepresentation],
3016 state: &EntityResolverState,
3017) -> Result<Vec<Option<serde_json::Value>>, async_graphql::Error> {
3018 if representations.is_empty() {
3019 return Ok(Vec::new());
3020 }
3021
3022 let pool = ctx.data::<PgPool>()?;
3023 let gql_ctx = ctx.data::<GraphQLContext>()?;
3024 let mut bound_values = Vec::new();
3025 let mut value_rows = Vec::with_capacity(representations.len());
3026 for (ordinality, representation) in representations.iter().enumerate() {
3027 let normalized_values = normalized_entity_key_values(
3028 &representation.values,
3029 &lookup.key_columns,
3030 &lookup.type_name,
3031 )?;
3032 let mut values = Vec::with_capacity(lookup.key_columns.len() + 1);
3033 for (key_column, value) in lookup.key_columns.iter().zip(normalized_values) {
3034 values.push(format!(
3035 "${}::{}",
3036 bound_values.len() + 1,
3037 key_column.pg_type
3038 ));
3039 bound_values.push(value);
3040 }
3041 values.push(ordinality.to_string());
3042 value_rows.push(format!("({})", values.join(", ")));
3043 }
3044
3045 let representation_alias = "pgrst_entity_representation";
3046 let ordinality_column = "pgrst_entity_ordinality";
3047 let key_aliases: Vec<String> = (0..lookup.key_columns.len())
3048 .map(|index| format!("pgrst_entity_key_{index}"))
3049 .collect();
3050 let mut representation_columns: Vec<String> = key_aliases
3051 .iter()
3052 .map(|column| postrust_sql::escape_ident(column))
3053 .collect();
3054 representation_columns.push(postrust_sql::escape_ident(ordinality_column));
3055 let representations_sql = format!(
3056 "(VALUES {}) AS {} ({})",
3057 value_rows.join(", "),
3058 postrust_sql::escape_ident(representation_alias),
3059 representation_columns.join(", ")
3060 );
3061 let key_predicate = lookup
3062 .key_columns
3063 .iter()
3064 .zip(&key_aliases)
3065 .map(|(key_column, key_alias)| {
3066 format!(
3067 "{}.{} = {}.{}",
3068 postrust_sql::escape_ident(READ_ROW),
3069 postrust_sql::escape_ident(&key_column.column_name),
3070 postrust_sql::escape_ident(representation_alias),
3071 postrust_sql::escape_ident(key_alias)
3072 )
3073 })
3074 .collect::<Vec<_>>()
3075 .join(" AND ");
3076 let mut where_sql = format!(" WHERE ({key_predicate})");
3077
3078 let permission = permission_predicate(
3079 &gql_ctx.caller(),
3080 state.names.as_ref(),
3081 &lookup.schema_name,
3082 &lookup.table_name,
3083 )?;
3084 if let Some(predicate) = permission {
3085 let guard = gql_ctx
3086 .schema_cache
3087 .get()
3088 .await
3089 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3090 let cache = guard
3091 .as_ref()
3092 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3093 let scope = WhereScope::table(
3094 &lookup.schema_name,
3095 &lookup.table_name,
3096 &lookup.type_name,
3097 state.names.as_ref(),
3098 )
3099 .under_alias(READ_ROW)
3100 .with_resolution(cache, state.relationships.as_ref())
3101 .for_caller(gql_ctx.caller());
3102 let (filter_sql, filter_values) =
3103 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3104 if !filter_sql.is_empty() {
3105 where_sql = format!(
3106 "{} AND ({})",
3107 where_sql,
3108 filter_sql.trim_start_matches("WHERE ")
3109 );
3110 bound_values.extend(filter_values);
3111 }
3112 }
3113
3114 let source = format!(
3115 "{}.{} AS {}",
3116 postrust_sql::escape_ident(&lookup.schema_name),
3117 postrust_sql::escape_ident(&lookup.table_name),
3118 postrust_sql::escape_ident(READ_ROW)
3119 );
3120 let source_rows = format!(
3121 "SELECT {}.* FROM {}{}",
3122 postrust_sql::escape_ident(READ_ROW),
3123 source,
3124 where_sql
3125 );
3126 let mut projection = {
3127 let guard = gql_ctx
3128 .schema_cache
3129 .get()
3130 .await
3131 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3132 guard
3133 .as_ref()
3134 .and_then(|cache| {
3135 cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3136 &lookup.schema_name,
3137 &lookup.table_name,
3138 ))
3139 })
3140 .and_then(|table| rename_projection(table, "src", state.names.as_ref()))
3141 .unwrap_or_else(|| "src.*".to_string())
3142 };
3143 let projected_rows = {
3144 let guard = gql_ctx
3145 .schema_cache
3146 .get()
3147 .await
3148 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3149 let cache = guard
3150 .as_ref()
3151 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3152 let table = cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3153 &lookup.schema_name,
3154 &lookup.table_name,
3155 ));
3156 let selected_fields = entity_selection_fields(ctx, &lookup.type_name);
3157 let mut param_idx = bound_values.len() + 1;
3158 let computed = match table {
3159 Some(table) => computed_projections(
3160 table,
3161 selected_fields.iter().copied(),
3162 "src",
3163 state.names.as_ref(),
3164 cache,
3165 &mut param_idx,
3166 &mut bound_values,
3167 )?,
3168 None => Vec::new(),
3169 };
3170 let embeds = build_embed_expressions(
3171 &gql_ctx.caller(),
3172 cache,
3173 state.relationships.as_ref(),
3174 &lookup.type_name,
3175 "src",
3176 selected_fields,
3177 state.max_rows,
3178 &mut 0,
3179 &mut param_idx,
3180 &mut bound_values,
3181 state.names.as_ref(),
3182 )?;
3183 for expression in &computed {
3184 projection.push_str(", ");
3185 projection.push_str(expression);
3186 }
3187 for (field_name, expression) in &embeds {
3188 projection.push_str(", ");
3189 projection.push_str(expression);
3190 projection.push_str(" AS ");
3191 projection.push_str(&postrust_sql::escape_ident(field_name));
3192 }
3193 format!("SELECT {} FROM ({}) AS src", projection, source_rows)
3194 };
3195 let sql = format!(
3200 "SELECT json_build_array({}.{}, {}) FROM {} CROSS JOIN LATERAL ({}) AS t",
3201 postrust_sql::escape_ident(representation_alias),
3202 postrust_sql::escape_ident(ordinality_column),
3203 row_json("t", &lookup.row_column_types),
3204 representations_sql,
3205 projected_rows,
3206 );
3207 let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3208 let rows = execute_query_on(&mut tx, &sql, &bound_values).await?;
3209 tx.commit().await?;
3210
3211 let mut by_ordinal = std::iter::repeat_with(|| None)
3212 .take(representations.len())
3213 .collect::<Vec<_>>();
3214 for result in rows {
3215 let serde_json::Value::Array(mut pair) = result else {
3216 return Err(async_graphql::Error::new(
3217 "entity query returned an invalid result",
3218 ));
3219 };
3220 if pair.len() != 2 {
3221 return Err(async_graphql::Error::new(
3222 "entity query returned an invalid result",
3223 ));
3224 }
3225 let row = pair.pop().expect("the result pair has two values");
3226 let ordinality = pair
3227 .pop()
3228 .and_then(|value| value.as_u64())
3229 .and_then(|value| usize::try_from(value).ok())
3230 .filter(|value| *value < by_ordinal.len())
3231 .ok_or_else(|| async_graphql::Error::new("entity query returned an invalid ordinal"))?;
3232 by_ordinal[ordinality] = Some(row);
3233 }
3234 Ok(by_ordinal)
3235}
3236
3237fn normalized_entity_key_values(
3241 values: &serde_json::Map<String, serde_json::Value>,
3242 key_columns: &[EntityKeyColumn],
3243 type_name: &str,
3244) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
3245 let mut key = Vec::with_capacity(key_columns.len());
3246 for key_column in key_columns {
3247 let value = values.get(&key_column.field_name).ok_or_else(|| {
3248 async_graphql::Error::new(format!(
3249 "entity representation for \"{}\" is missing key field \"{}\"",
3250 type_name, key_column.field_name
3251 ))
3252 })?;
3253 key.push(normalize_entity_key_value(type_name, key_column, value)?);
3254 }
3255 Ok(key)
3256}
3257
3258fn normalize_entity_key_value(
3259 type_name: &str,
3260 key_column: &EntityKeyColumn,
3261 value: &serde_json::Value,
3262) -> Result<serde_json::Value, async_graphql::Error> {
3263 let invalid = |expected: &str| {
3264 async_graphql::Error::new(format!(
3265 "entity representation for \"{}\" has invalid key field \"{}\": expected {}, found {}",
3266 type_name,
3267 key_column.field_name,
3268 expected,
3269 json_value_kind(value)
3270 ))
3271 };
3272
3273 if key_column.explicit_id {
3274 return normalize_graphql_id(value, &key_column.pg_type).ok_or_else(|| {
3275 invalid(&format!(
3276 "an ID compatible with PostgreSQL type \"{}\"",
3277 key_column.pg_type
3278 ))
3279 });
3280 }
3281
3282 let pg_type = key_column.pg_type.trim().to_ascii_lowercase();
3283 let normalized = if matches!(pg_type.as_str(), "int8" | "bigint") {
3284 match value {
3285 serde_json::Value::Number(number) => number.as_i64(),
3286 serde_json::Value::String(text) => text.parse::<i64>().ok(),
3287 _ => None,
3288 }
3289 .map(serde_json::Value::from)
3290 } else if integer_bounds(&pg_type).is_some() {
3291 value.as_i64().and_then(|integer| {
3292 let (min, max) = integer_bounds(&pg_type)?;
3293 (min..=max)
3294 .contains(&integer)
3295 .then(|| serde_json::Value::from(integer))
3296 })
3297 } else if matches!(
3298 pg_type.as_str(),
3299 "float4" | "float8" | "real" | "double precision"
3300 ) {
3301 value
3302 .as_f64()
3303 .and_then(serde_json::Number::from_f64)
3304 .map(serde_json::Value::Number)
3305 } else if matches!(pg_type.as_str(), "numeric" | "decimal") {
3306 match value {
3307 serde_json::Value::Number(number) => {
3308 Some(serde_json::Value::String(number.to_string()))
3309 }
3310 serde_json::Value::String(text) if text.parse::<sqlx::types::BigDecimal>().is_ok() => {
3311 Some(serde_json::Value::String(text.clone()))
3312 }
3313 _ => None,
3314 }
3315 } else if matches!(pg_type.as_str(), "bool" | "boolean") {
3316 value.as_bool().map(serde_json::Value::Bool)
3317 } else if matches!(pg_type.as_str(), "json" | "jsonb") {
3318 Some(value.clone())
3319 } else if is_array_type(&pg_type) {
3320 value.is_array().then(|| value.clone())
3321 } else {
3322 value
3323 .as_str()
3324 .map(|text| serde_json::Value::String(text.to_string()))
3325 };
3326
3327 normalized.ok_or_else(|| {
3328 invalid(&format!(
3329 "a value compatible with PostgreSQL type \"{}\"",
3330 key_column.pg_type
3331 ))
3332 })
3333}
3334
3335fn normalize_graphql_id(value: &serde_json::Value, pg_type: &str) -> Option<serde_json::Value> {
3336 if integer_bounds(pg_type).is_some() {
3337 let integer = match value {
3338 serde_json::Value::Number(number) => number.as_i64(),
3339 serde_json::Value::String(text) => text.parse::<i64>().ok(),
3340 _ => None,
3341 }?;
3342 let (min, max) = integer_bounds(pg_type)?;
3343 return (min..=max)
3344 .contains(&integer)
3345 .then(|| serde_json::Value::from(integer));
3346 }
3347 if pg_type.eq_ignore_ascii_case("uuid") {
3348 return normalize_uuid(value).map(serde_json::Value::String);
3349 }
3350 if is_text_type(pg_type) {
3351 return match value {
3352 serde_json::Value::String(s) => Some(serde_json::Value::String(s.clone())),
3353 serde_json::Value::Number(n) if n.is_i64() || n.is_u64() => {
3354 Some(serde_json::Value::String(n.to_string()))
3355 }
3356 _ => None,
3357 };
3358 }
3359 None
3360}
3361
3362fn integer_bounds(pg_type: &str) -> Option<(i64, i64)> {
3363 match pg_type.trim().to_ascii_lowercase().as_str() {
3364 "int2" | "smallint" => Some((i16::MIN.into(), i16::MAX.into())),
3365 "int4" | "int" | "integer" => Some((i32::MIN.into(), i32::MAX.into())),
3366 "int8" | "bigint" => Some((i64::MIN, i64::MAX)),
3367 _ => None,
3368 }
3369}
3370
3371fn is_text_type(pg_type: &str) -> bool {
3372 matches!(
3373 pg_type.trim().to_ascii_lowercase().as_str(),
3374 "text" | "varchar" | "character varying" | "char" | "character" | "bpchar" | "citext"
3375 )
3376}
3377
3378fn normalize_uuid(value: &serde_json::Value) -> Option<String> {
3379 sqlx::types::Uuid::parse_str(value.as_str()?)
3380 .ok()
3381 .map(|uuid| uuid.to_string())
3382}
3383
3384fn json_value_kind(value: &serde_json::Value) -> &'static str {
3385 match value {
3386 serde_json::Value::Null => "null",
3387 serde_json::Value::Bool(_) => "a boolean",
3388 serde_json::Value::Number(_) => "a number",
3389 serde_json::Value::String(_) => "a string",
3390 serde_json::Value::Array(_) => "an array",
3391 serde_json::Value::Object(_) => "an object",
3392 }
3393}
3394
3395fn add_function_fields(
3402 mut root: Object,
3403 generated: &GeneratedSchema,
3404 volatile: bool,
3405 max_rows: Option<i64>,
3406 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3407 names: Arc<crate::names::NameOverrides>,
3408) -> Object {
3409 let taken: HashSet<&str> = if volatile {
3414 generated
3415 .mutation_fields
3416 .iter()
3417 .map(|f| f.name.as_str())
3418 .collect()
3419 } else {
3420 generated
3421 .query_fields
3422 .iter()
3423 .map(|f| f.name.as_str())
3424 .collect()
3425 };
3426
3427 for function in &generated.function_fields {
3428 if function.volatile != volatile {
3429 continue;
3430 }
3431 if taken.contains(function.name.as_str()) {
3432 tracing::warn!(
3433 "not exposing {}.{}({}) -- a table already has a root field \
3434 called {}",
3435 function.schema_name,
3436 function.function_name,
3437 function.argument_signature(),
3438 function.name,
3439 );
3440 continue;
3441 }
3442 let spec = Arc::new(QueryFieldSpec {
3443 schema_name: function.returns_table.0.clone(),
3444 table_name: function.returns_table.1.clone(),
3445 type_name: function.returns.clone(),
3446 is_by_pk: false,
3447 pk_columns: Vec::new(),
3448 max_rows,
3449 relationships: Arc::clone(&relationships),
3450 names: Arc::clone(&names),
3451 call: Some(Arc::new(FunctionCall {
3452 schema: function.schema_name.clone(),
3453 name: function.function_name.clone(),
3454 arguments: function.arguments.clone(),
3455 session_argument: function.session_argument.clone(),
3456 })),
3457 });
3458
3459 let mut field = Field::new(
3460 &function.name,
3461 TypeRef::named_nn_list_nn(&function.returns),
3462 move |ctx| {
3463 let spec = Arc::clone(&spec);
3464 FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
3465 },
3466 );
3467 if !function.arguments.is_empty() {
3468 field = field.argument(InputValue::new(
3469 "args",
3470 TypeRef::named_nn(format!("{}_args", function.name)),
3471 ));
3472 }
3473 field = field
3474 .argument(InputValue::new(
3475 "where",
3476 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
3477 &function.local_returns,
3478 )),
3479 ))
3480 .argument(InputValue::new(
3481 "order_by",
3482 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
3483 &function.local_returns,
3484 )),
3485 ))
3486 .argument(InputValue::new(
3487 "distinct_on",
3488 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
3489 &function.local_returns,
3490 )),
3491 ))
3492 .argument(InputValue::new("limit", TypeRef::named("Int")))
3493 .argument(InputValue::new("offset", TypeRef::named("Int")));
3494 if let Some(description) = &function.description {
3495 field = field.description(description);
3496 }
3497 root = root.field(field);
3498
3499 let agg_spec = Arc::new(AggregateSpec {
3504 schema_name: function.returns_table.0.clone(),
3505 table_name: function.returns_table.1.clone(),
3506 type_name: function.returns.clone(),
3507 max_rows,
3508 relationships: Arc::clone(&relationships),
3509 names: Arc::clone(&names),
3510 call: Some(Arc::new(FunctionCall {
3511 schema: function.schema_name.clone(),
3512 name: function.function_name.clone(),
3513 arguments: function.arguments.clone(),
3514 session_argument: function.session_argument.clone(),
3515 })),
3516 });
3517 let mut aggregate = Field::new(
3518 format!("{}_aggregate", function.name),
3519 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
3520 &function.local_returns,
3521 )),
3522 move |ctx| {
3523 let agg_spec = Arc::clone(&agg_spec);
3524 FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
3525 },
3526 );
3527 if !function.arguments.is_empty() {
3528 aggregate = aggregate.argument(InputValue::new(
3529 "args",
3530 TypeRef::named_nn(format!("{}_args", function.name)),
3531 ));
3532 }
3533 aggregate = with_row_arguments(aggregate, &function.local_returns).description(format!(
3534 "fetch aggregated fields from the table: \"{}\"",
3535 function.returns_table.1
3536 ));
3537 root = root.field(aggregate);
3538 }
3539 root
3540}
3541
3542fn function_source(
3550 ctx: &ResolverContext<'_>,
3551 call: &FunctionCall,
3552 alias: &str,
3553 bound_values: &mut Vec<serde_json::Value>,
3554) -> Result<String, async_graphql::Error> {
3555 let given = ctx
3556 .args
3557 .try_get("args")
3558 .ok()
3559 .map(|v| accessor_to_json(&v))
3560 .unwrap_or_else(|| serde_json::json!({}));
3561 let mut passed = Vec::new();
3562 for (name, _, required) in &call.arguments {
3563 match given.get(name) {
3564 None if *required => {
3565 return Err(async_graphql::Error::new(format!(
3566 "{} needs the argument \"{}\"",
3567 call.name, name
3568 )))
3569 }
3570 None => continue,
3573 Some(value) => {
3574 let (_, pg_type, _) = call
3581 .arguments
3582 .iter()
3583 .find(|(argument, _, _)| argument == name)
3584 .expect("iterating the arguments");
3585 passed.push(format!(
3586 "{} => ${}::{}",
3587 postrust_sql::escape_ident(name),
3588 bound_values.len() + 1,
3589 pg_type
3590 ));
3591 bound_values.push(value.clone());
3592 }
3593 }
3594 }
3595 if let Some(session) = &call.session_argument {
3599 passed.push(format!(
3600 "{} => {}",
3601 postrust_sql::escape_ident(session),
3602 SESSION_ARGUMENT
3603 ));
3604 }
3605 Ok(format!(
3606 "{}.{}({}) AS {}",
3607 postrust_sql::escape_ident(&call.schema),
3608 postrust_sql::escape_ident(&call.name),
3609 passed.join(", "),
3610 postrust_sql::escape_ident(alias)
3611 ))
3612}
3613
3614struct FunctionCall {
3616 schema: String,
3617 name: String,
3618 arguments: Vec<(String, String, bool)>,
3619 session_argument: Option<String>,
3622}
3623
3624struct AggregateSpec {
3626 schema_name: String,
3627 table_name: String,
3628 type_name: String,
3629 max_rows: Option<i64>,
3630 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3631 names: Arc<crate::names::NameOverrides>,
3632 call: Option<Arc<FunctionCall>>,
3636}
3637
3638async fn resolve_aggregate<'a>(
3645 ctx: &ResolverContext<'a>,
3646 spec: &AggregateSpec,
3647) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3648 Ok(Some(FieldValue::value(aggregate_value(ctx, spec).await?)))
3649}
3650
3651async fn aggregate_value(
3656 ctx: &ResolverContext<'_>,
3657 spec: &AggregateSpec,
3658) -> Result<Value, async_graphql::Error> {
3659 use crate::schema::aggregate as agg;
3660
3661 let pool = ctx.data::<PgPool>()?;
3662 let gql_ctx = ctx.data::<GraphQLContext>()?;
3663
3664 let mut bound_values: Vec<serde_json::Value> = Vec::new();
3665 let source = match &spec.call {
3669 None => format!(
3670 "{}.{}",
3671 postrust_sql::escape_ident(&spec.schema_name),
3672 postrust_sql::escape_ident(&spec.table_name)
3673 ),
3674 Some(call) => function_source(ctx, call, &spec.table_name, &mut bound_values)?,
3675 };
3676
3677 let mut where_sql = String::new();
3678 {
3679 let requested = ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v));
3683 let permission = permission_predicate(
3684 &gql_ctx.caller(),
3685 spec.names.as_ref(),
3686 &spec.schema_name,
3687 &spec.table_name,
3688 )?;
3689 if let Some(predicate) = and_predicates(requested, permission) {
3690 let guard = gql_ctx
3691 .schema_cache
3692 .get()
3693 .await
3694 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3695 let cache = guard
3696 .as_ref()
3697 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3698 let scope = match &spec.call {
3705 None => WhereScope::table(
3706 &spec.schema_name,
3707 &spec.table_name,
3708 &spec.type_name,
3709 spec.names.as_ref(),
3710 )
3711 .with_resolution(cache, spec.relationships.as_ref()),
3712 Some(_) => WhereScope::for_alias(
3713 &spec.schema_name,
3714 &spec.table_name,
3715 &spec.table_name,
3716 &spec.type_name,
3717 cache,
3718 spec.relationships.as_ref(),
3719 spec.names.as_ref(),
3720 ),
3721 }
3722 .for_caller(gql_ctx.caller());
3723 let (sql, values) =
3724 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3725 if !sql.is_empty() {
3726 where_sql = format!(" {}", sql);
3727 bound_values.extend(values);
3728 }
3729 }
3730 }
3731
3732 let order_sql = build_order_by_clause(
3733 ctx,
3734 &gql_ctx.schema_cache,
3735 &spec.schema_name,
3736 &spec.table_name,
3737 &spec.type_name,
3738 spec.relationships.as_ref(),
3739 &format!(
3740 "{}.{}",
3741 postrust_sql::escape_ident(&spec.schema_name),
3742 postrust_sql::escape_ident(&spec.table_name)
3743 ),
3744 spec.names.as_ref(),
3745 )
3746 .await?;
3747
3748 let requested_limit = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
3749 let offset = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
3750 let ceiling = match (
3751 spec.max_rows,
3752 permission_limit(
3753 &gql_ctx.caller(),
3754 spec.names.as_ref(),
3755 &spec.schema_name,
3756 &spec.table_name,
3757 ),
3758 ) {
3759 (Some(configured), Some(granted)) => Some(configured.min(granted)),
3760 (only, None) | (None, only) => only,
3761 };
3762 let limit = match (requested_limit, ceiling) {
3763 (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
3764 (Some(requested), None) => Some(requested),
3765 (None, ceiling) => ceiling,
3766 };
3767
3768 let rows = format!("SELECT * FROM {}{}{}", source, where_sql, order_sql);
3769
3770 let mut inner = rows.clone();
3772 if let Some(limit) = limit {
3773 inner.push_str(&format!(" LIMIT {}", limit));
3774 }
3775 if let Some(offset) = offset {
3776 inner.push_str(&format!(" OFFSET {}", offset));
3777 }
3778
3779 let mut counted = rows;
3793 if let Some(limit) = requested_limit {
3794 counted.push_str(&format!(" LIMIT {}", limit));
3795 }
3796 if let Some(offset) = offset {
3797 counted.push_str(&format!(" OFFSET {}", offset));
3798 }
3799
3800 let mut wants_nodes = false;
3805 let mut wanted: BTreeMap<String, Vec<String>> = BTreeMap::new();
3806 let mut counts: Vec<(String, String)> = Vec::new();
3809 for selection in ctx.field().selection_set() {
3810 match selection.name() {
3811 "nodes" => wants_nodes = true,
3812 "aggregate" => {
3813 for function in selection.selection_set() {
3814 if function.name() == "count" {
3815 counts.push((
3816 function.alias().unwrap_or("count").to_string(),
3817 count_expression(
3818 function,
3819 (&spec.schema_name, &spec.table_name),
3820 None,
3821 spec.names.as_ref(),
3822 ),
3823 ));
3824 continue;
3825 }
3826 let columns = wanted.entry(function.name().to_string()).or_default();
3827 for column in function.selection_set() {
3828 let name = column.name().to_string();
3829 if !columns.contains(&name) {
3830 columns.push(name);
3831 }
3832 }
3833 }
3834 }
3835 _ => {}
3836 }
3837 }
3838 wanted.retain(|_, columns| !columns.is_empty());
3839
3840 let mut result = async_graphql::indexmap::IndexMap::new();
3841
3842 if !counts.is_empty() || !wanted.is_empty() {
3843 let mut parts = match counts.iter().any(|(key, _)| key == "count") {
3847 true => Vec::new(),
3848 false => vec!["'count', count(*)".to_string()],
3849 };
3850 for (key, expression) in &counts {
3851 parts.push(format!("'{}', {}", key.replace('\'', "''"), expression));
3852 }
3853 for (function, columns) in &wanted {
3854 let sql_function = agg::NUMERIC_AGGREGATES
3858 .iter()
3859 .chain(agg::ORDERED_AGGREGATES.iter())
3860 .find(|(name, _)| name == function)
3861 .map(|(name, _)| *name)
3862 .ok_or_else(|| {
3863 async_graphql::Error::new(format!("unknown aggregate \"{}\"", function))
3864 })?;
3865 let per_column: Vec<String> = columns
3866 .iter()
3867 .map(|field| {
3868 let source = spec
3869 .names
3870 .column_source(&spec.schema_name, &spec.table_name, field)
3871 .unwrap_or(field);
3872 format!(
3873 "'{}', {}({})",
3874 field.replace('\'', "''"),
3875 sql_function,
3876 postrust_sql::escape_ident(source)
3877 )
3878 })
3879 .collect();
3880 parts.push(format!(
3881 "'{}', json_build_object({})",
3882 sql_function,
3883 per_column.join(", ")
3884 ));
3885 }
3886
3887 let sql = format!(
3888 "SELECT json_build_object({}) FROM ({}) AS pgrst_agg",
3889 parts.join(", "),
3890 counted
3891 );
3892 let mut conn =
3893 begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3894 let rows = execute_query_on(&mut conn, &sql, &bound_values).await?;
3895 conn.commit().await?;
3896 if let Some(first) = rows.into_iter().next() {
3897 if let Value::Object(map) = json_to_value(first) {
3898 result.insert(async_graphql::Name::new("aggregate"), Value::Object(map));
3899 }
3900 }
3901 }
3902
3903 if wants_nodes {
3904 let nodes = ctx
3910 .field()
3911 .selection_set()
3912 .find(|selection| selection.name() == "nodes")
3913 .ok_or_else(|| async_graphql::Error::new("nodes was asked for and then was not"))?;
3914
3915 let mut node_values = bound_values.clone();
3919 let (projection, row_column_types) = {
3920 let guard = gql_ctx
3921 .schema_cache
3922 .get()
3923 .await
3924 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3925 let cache = guard
3926 .as_ref()
3927 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3928 let qi = postrust_core::api_request::QualifiedIdentifier::new(
3929 &spec.schema_name,
3930 &spec.table_name,
3931 );
3932 let table = cache.get_table(&qi);
3933 let mut param_idx = node_values.len() + 1;
3934 let computed = match table {
3935 Some(table) => computed_projections(
3936 table,
3937 nodes.selection_set(),
3938 "src",
3939 spec.names.as_ref(),
3940 cache,
3941 &mut param_idx,
3942 &mut node_values,
3943 )?,
3944 None => Vec::new(),
3945 };
3946 let embeds = build_embed_expressions(
3947 &gql_ctx.caller(),
3948 cache,
3949 spec.relationships.as_ref(),
3950 &spec.type_name,
3951 "src",
3952 nodes.selection_set(),
3953 spec.max_rows,
3954 &mut 0,
3955 &mut param_idx,
3956 &mut node_values,
3957 spec.names.as_ref(),
3958 )?;
3959 let mut projection = table
3963 .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
3964 .unwrap_or_else(|| "src.*".to_string());
3965 for expression in &computed {
3966 projection.push_str(", ");
3967 projection.push_str(expression);
3968 }
3969 for (field_name, expression) in &embeds {
3970 projection.push_str(", ");
3971 projection.push_str(expression);
3972 projection.push_str(" AS ");
3973 projection.push_str(&postrust_sql::escape_ident(field_name));
3974 }
3975 (
3976 projection,
3977 table
3978 .map(|table| exposed_column_types(table, spec.names.as_ref()))
3979 .unwrap_or_default(),
3980 )
3981 };
3982
3983 let sql = format!(
3984 "SELECT {} FROM (SELECT {} FROM ({}) AS src) AS pgrst_nodes",
3985 row_json("pgrst_nodes", &row_column_types),
3986 projection,
3987 inner
3988 );
3989 let mut conn =
3990 begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3991 let rows = execute_query_on(&mut conn, &sql, &node_values).await?;
3992 conn.commit().await?;
3993 result.insert(
3994 async_graphql::Name::new("nodes"),
3995 Value::List(rows.into_iter().map(json_to_value).collect()),
3996 );
3997 }
3998
3999 let _ = &spec.type_name;
4000 Ok(Value::Object(result))
4001}
4002
4003async fn resolve_query<'a>(
4005 ctx: &ResolverContext<'a>,
4006 spec: &QueryFieldSpec,
4007) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4008 Ok(rows_as_field_value(
4009 query_rows(ctx, spec).await?,
4010 spec.is_by_pk,
4011 ))
4012}
4013
4014async fn query_rows(
4020 ctx: &ResolverContext<'_>,
4021 spec: &QueryFieldSpec,
4022) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4023 let schema_name = spec.schema_name.as_str();
4024 let table_name = spec.table_name.as_str();
4025 let type_name = spec.type_name.as_str();
4026 let is_by_pk = spec.is_by_pk;
4027 let pk_columns = spec.pk_columns.as_slice();
4028 let max_rows = spec.max_rows;
4029 let relationships = spec.relationships.as_ref();
4030
4031 let pool = ctx.data::<PgPool>()?;
4032 let gql_ctx = ctx.data::<GraphQLContext>()?;
4033
4034 debug!("Resolving query for table: {}", table_name);
4035
4036 let requested_limit: Option<i64> = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
4038
4039 let offset: Option<i64> = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
4040
4041 let limit: Option<i64> = if is_by_pk {
4046 Some(1)
4047 } else {
4048 let ceiling = match (
4052 max_rows,
4053 permission_limit(
4054 &gql_ctx.caller(),
4055 spec.names.as_ref(),
4056 schema_name,
4057 table_name,
4058 ),
4059 ) {
4060 (Some(configured), Some(granted)) => Some(configured.min(granted)),
4061 (only, None) | (None, only) => only,
4062 };
4063 match (requested_limit, ceiling) {
4064 (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
4065 (Some(requested), None) => Some(requested),
4066 (None, ceiling) => ceiling,
4067 }
4068 };
4069
4070 let mut where_sql = String::new();
4078 let mut bound_values: Vec<serde_json::Value> = Vec::new();
4079
4080 let source = match &spec.call {
4084 None => format!(
4090 "{}.{} AS {}",
4091 postrust_sql::escape_ident(schema_name),
4092 postrust_sql::escape_ident(table_name),
4093 postrust_sql::escape_ident(READ_ROW)
4094 ),
4095 Some(call) => function_source(ctx, call, table_name, &mut bound_values)?,
4096 };
4097
4098 let source_ref = match &spec.call {
4101 None => postrust_sql::escape_ident(READ_ROW),
4102 Some(_) => postrust_sql::escape_ident(table_name),
4103 };
4104
4105 if is_by_pk {
4106 if pk_columns.is_empty() {
4107 return Err(async_graphql::Error::new(format!(
4108 "\"{}\" has no primary key, so it cannot be queried by key",
4109 table_name
4110 )));
4111 }
4112
4113 let mut conditions = Vec::with_capacity(pk_columns.len());
4114 for (idx, (col_name, pg_type)) in pk_columns.iter().enumerate() {
4115 let field = spec
4116 .names
4117 .column(schema_name, table_name, col_name)
4118 .unwrap_or(col_name);
4119 let value = ctx.args.try_get(field).map_err(|_| {
4120 async_graphql::Error::new(format!(
4121 "missing required primary key argument \"{}\"",
4122 field
4123 ))
4124 })?;
4125
4126 conditions.push(format!(
4127 "{} = ${}::{}",
4128 postrust_sql::escape_ident(col_name),
4129 idx + 1,
4130 pg_type
4131 ));
4132 bound_values.push(accessor_to_json(&value));
4133 }
4134 where_sql = format!(" WHERE {}", conditions.join(" AND "));
4135 }
4136
4137 {
4142 let requested = match is_by_pk {
4143 true => None,
4144 false => ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v)),
4145 };
4146 let permission = permission_predicate(
4147 &gql_ctx.caller(),
4148 spec.names.as_ref(),
4149 schema_name,
4150 table_name,
4151 )?;
4152
4153 if let Some(predicate) = and_predicates(requested, permission) {
4154 let guard = gql_ctx
4155 .schema_cache
4156 .get()
4157 .await
4158 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4159 let cache = guard
4160 .as_ref()
4161 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4162 let scope = match &spec.call {
4163 None => WhereScope::table(schema_name, table_name, type_name, spec.names.as_ref())
4164 .under_alias(READ_ROW)
4165 .with_resolution(cache, relationships)
4166 .for_caller(gql_ctx.caller()),
4167 Some(_) => WhereScope::for_alias(
4170 schema_name,
4171 table_name,
4172 table_name,
4173 type_name,
4174 cache,
4175 relationships,
4176 spec.names.as_ref(),
4177 )
4178 .for_caller(gql_ctx.caller()),
4179 };
4180 let (filter_sql, filter_values) =
4181 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
4182 if !filter_sql.is_empty() {
4183 where_sql = match where_sql.is_empty() {
4186 true => format!(" {}", filter_sql),
4187 false => format!(
4188 "{} AND ({})",
4189 where_sql,
4190 filter_sql.trim_start_matches("WHERE ")
4191 ),
4192 };
4193 bound_values.extend(filter_values);
4194 }
4195 }
4196 }
4197
4198 let (order_sql, distinct_on) = if is_by_pk {
4201 (String::new(), Vec::new())
4202 } else {
4203 (
4204 build_order_by_clause(
4205 ctx,
4206 &gql_ctx.schema_cache,
4207 schema_name,
4208 table_name,
4209 type_name,
4210 relationships,
4211 &source_ref,
4212 spec.names.as_ref(),
4213 )
4214 .await?,
4215 build_distinct_on(
4216 ctx,
4217 &gql_ctx.schema_cache,
4218 schema_name,
4219 table_name,
4220 spec.names.as_ref(),
4221 )
4222 .await?,
4223 )
4224 };
4225
4226 let (distinct_sql, order_sql) = {
4234 let written = order_sql.strip_prefix(" ORDER BY ");
4235 let (prefix, order) = distinct_on_clause(&distinct_on, written).map_err(|error| {
4238 at_path(
4239 error,
4240 &format!(
4241 "$.selectionSet.{}.args",
4242 ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4243 ),
4244 )
4245 })?;
4246 (
4247 prefix,
4248 match order {
4249 Some(order) => format!(" ORDER BY {}", order),
4250 None => String::new(),
4251 },
4252 )
4253 };
4254
4255 let (computed, row_column_types) = {
4258 let guard = gql_ctx
4259 .schema_cache
4260 .get()
4261 .await
4262 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4263 match guard.as_ref() {
4264 Some(cache) => {
4265 let qi =
4266 postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
4267 match cache.get_table(&qi) {
4268 Some(table) => {
4269 let mut param_idx = bound_values.len() + 1;
4270 (
4271 computed_projections(
4272 table,
4273 ctx.field().selection_set(),
4274 "src",
4275 spec.names.as_ref(),
4276 cache,
4277 &mut param_idx,
4278 &mut bound_values,
4279 )?,
4280 exposed_column_types(table, spec.names.as_ref()),
4281 )
4282 }
4283 None => (Vec::new(), HashMap::new()),
4284 }
4285 }
4286 None => (Vec::new(), HashMap::new()),
4287 }
4288 };
4289 let mut inner = format!(
4293 "SELECT {}* FROM {}{}{}",
4294 distinct_sql, source, where_sql, order_sql
4295 );
4296
4297 if let Some(limit) = limit {
4298 inner.push_str(&format!(" LIMIT {}", limit));
4299 }
4300
4301 if let Some(offset) = offset {
4302 inner.push_str(&format!(" OFFSET {}", offset));
4303 }
4304
4305 let embed_expressions = {
4308 let guard = gql_ctx
4309 .schema_cache
4310 .get()
4311 .await
4312 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4313 match guard.as_ref() {
4314 Some(cache) => {
4315 let mut param_idx = bound_values.len() + 1;
4320 build_embed_expressions(
4321 &gql_ctx.caller(),
4322 cache,
4323 relationships,
4324 type_name,
4325 "src",
4326 ctx.field().selection_set(),
4327 max_rows,
4328 &mut 0,
4329 &mut param_idx,
4330 &mut bound_values,
4331 spec.names.as_ref(),
4332 )?
4333 }
4334 None => Vec::new(),
4335 }
4336 };
4337
4338 let renamed = {
4346 let guard = gql_ctx
4347 .schema_cache
4348 .get()
4349 .await
4350 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4351 guard
4352 .as_ref()
4353 .and_then(|cache| {
4354 cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
4355 schema_name,
4356 table_name,
4357 ))
4358 })
4359 .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4360 };
4361 let inner = if embed_expressions.is_empty() && computed.is_empty() && renamed.is_none() {
4362 inner
4363 } else {
4364 let mut projection = renamed.unwrap_or_else(|| "src.*".to_string());
4365 for expression in &computed {
4366 projection.push_str(", ");
4367 projection.push_str(expression);
4368 }
4369 for (field_name, expression) in &embed_expressions {
4370 projection.push_str(", ");
4371 projection.push_str(expression);
4372 projection.push_str(" AS ");
4373 projection.push_str(&postrust_sql::escape_ident(field_name));
4374 }
4375 format!("SELECT {} FROM ({}) AS src", projection, inner)
4376 };
4377
4378 let sql = format!(
4379 "SELECT {} FROM ({}) t",
4380 row_json("t", &row_column_types),
4381 inner
4382 );
4383
4384 let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
4388
4389 let mut result = execute_query_on(&mut tx, &sql, &bound_values).await?;
4391
4392 if embed_expressions.is_empty() {
4394 let guard = gql_ctx
4395 .schema_cache
4396 .get()
4397 .await
4398 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4399 let cache = guard
4400 .as_ref()
4401 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4402
4403 let embed_ctx = EmbedContext {
4404 schema_cache: cache,
4405 relationships,
4406 max_rows,
4407 };
4408
4409 embed_relationships(&mut tx, &embed_ctx, type_name, ctx.field(), &mut result).await?;
4410 }
4411
4412 tx.commit().await?;
4413
4414 Ok(result)
4415}
4416
4417fn rows_as_field_value<'a>(rows: Vec<serde_json::Value>, is_by_pk: bool) -> Option<FieldValue<'a>> {
4422 match is_by_pk {
4423 true => rows
4424 .into_iter()
4425 .next()
4426 .map(|v| FieldValue::value(json_to_value(v))),
4427 false => Some(FieldValue::list(
4428 rows.into_iter()
4429 .map(|v| FieldValue::value(json_to_value(v))),
4430 )),
4431 }
4432}
4433
4434#[allow(clippy::too_many_arguments)]
4436async fn resolve_mutation<'a>(
4437 ctx: &ResolverContext<'a>,
4438 schema_name: &str,
4439 table_name: &str,
4440 mutation_type: MutationType,
4441 pk_columns: &[(String, String)],
4442 relationships: &Arc<HashMap<String, Vec<RelationshipField>>>,
4443 type_names: &Arc<HashMap<(String, String), String>>,
4444 names: &Arc<crate::names::NameOverrides>,
4445 max_rows: Option<i64>,
4446) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4447 let pool = ctx.data::<PgPool>()?;
4448 let gql_ctx = ctx.data::<GraphQLContext>()?;
4449
4450 debug!(
4451 "Resolving mutation for table: {} type: {:?}",
4452 table_name, mutation_type
4453 );
4454
4455 let type_name = type_names
4461 .get(&(schema_name.to_string(), table_name.to_string()))
4462 .cloned()
4463 .unwrap_or_else(|| table_name.to_string());
4464
4465 let by_key = matches!(
4466 mutation_type,
4467 MutationType::InsertOne | MutationType::UpdateByPk | MutationType::DeleteByPk
4468 );
4469 let returning = if by_key {
4470 Some(ctx.field())
4471 } else {
4472 ctx.field()
4473 .selection_set()
4474 .find(|selection| selection.name() == "returning")
4475 };
4476
4477 if mutation_type == MutationType::UpdateMany {
4482 let updates = ctx
4483 .args
4484 .try_get("updates")
4485 .ok()
4486 .map(|v| accessor_to_json(&v))
4487 .unwrap_or(serde_json::Value::Null);
4488 let serde_json::Value::Array(entries) = updates else {
4489 return Err(async_graphql::Error::new(
4490 "\"updates\" takes a list of {where, _set, …} objects",
4491 ));
4492 };
4493 let column_types = column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await;
4494 let mut answers: Vec<FieldValue<'_>> = Vec::with_capacity(entries.len());
4495 for entry in entries {
4496 let serde_json::Value::Object(entry) = entry else {
4497 return Err(async_graphql::Error::new(
4498 "each update is an object of {where, _set, …}",
4499 ));
4500 };
4501 let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4502 .iter()
4503 .filter_map(|name| entry.get(*name).map(|value| (*name, value.clone())))
4504 .filter(|(_, value)| !value.is_null())
4505 .collect();
4506 let rows = execute_update(
4507 pool,
4508 gql_ctx,
4509 schema_name,
4510 table_name,
4511 &type_name,
4512 operators,
4513 column_types.clone(),
4514 entry.get("where").cloned(),
4515 relationships.as_ref(),
4516 names.as_ref(),
4517 )
4518 .await?;
4519 let affected = rows.len();
4520 let rows = match returning {
4521 Some(returning) if !rows.is_empty() => {
4522 reread_returning(
4523 pool,
4524 gql_ctx,
4525 schema_name,
4526 table_name,
4527 &type_name,
4528 rows,
4529 returning,
4530 relationships.as_ref(),
4531 names.as_ref(),
4532 max_rows,
4533 )
4534 .await?
4535 }
4536 _ => rows,
4537 };
4538 answers.extend(mutation_result(rows, affected, false));
4539 }
4540 return Ok(Some(FieldValue::list(answers)));
4541 }
4542
4543 let (result, affected) = match mutation_type {
4544 MutationType::Insert | MutationType::InsertOne => {
4545 let objects = ctx
4546 .args
4547 .try_get("objects")
4548 .ok()
4549 .map(|v| accessor_to_json(&v))
4550 .or_else(|| {
4551 ctx.args
4554 .try_get("object")
4555 .ok()
4556 .map(|v| serde_json::Value::Array(vec![accessor_to_json(&v)]))
4557 })
4558 .unwrap_or_else(|| serde_json::Value::Array(vec![]));
4559
4560 let guard = gql_ctx
4561 .schema_cache
4562 .get()
4563 .await
4564 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4565 let cache = guard
4566 .as_ref()
4567 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4568 let here = format!(
4572 "$.selectionSet.{}",
4573 ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4574 );
4575 let named = match mutation_type {
4578 MutationType::InsertOne => "object",
4579 _ => "objects",
4580 };
4581 let context = InsertContext {
4582 on_conflict: ctx
4583 .args
4584 .try_get("on_conflict")
4585 .ok()
4586 .map(|v| accessor_to_json(&v))
4587 .filter(|v| !v.is_null()),
4588 cache,
4589 relationships: relationships.as_ref(),
4590 type_names: type_names.as_ref(),
4591 names: names.as_ref(),
4592 caller: gql_ctx.caller(),
4593 row: String::new(),
4596 objects: format!("{}.args.{}", here, named),
4597 conflict: format!("{}.args.on_conflict", here),
4598 };
4599
4600 execute_insert(
4601 pool,
4602 gql_ctx,
4603 schema_name,
4604 table_name,
4605 objects,
4606 &context,
4607 mutation_type == MutationType::InsertOne,
4608 )
4609 .await?
4610 }
4611 MutationType::UpdateMany | MutationType::Update | MutationType::UpdateByPk => {
4614 let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4618 .iter()
4619 .filter_map(|name| {
4620 ctx.args
4621 .try_get(name)
4622 .ok()
4623 .map(|v| (*name, accessor_to_json(&v)))
4624 })
4625 .filter(|(_, value)| !value.is_null())
4626 .collect();
4627
4628 let where_clause = if mutation_type == MutationType::UpdateByPk {
4629 Some(pk_where_from_args(
4630 ctx,
4631 schema_name,
4632 table_name,
4633 pk_columns,
4634 names.as_ref(),
4635 )?)
4636 } else {
4637 ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4638 };
4639 let where_clause = and_predicates(
4643 where_clause,
4644 permission_filter(
4645 &gql_ctx.caller(),
4646 names.as_ref(),
4647 schema_name,
4648 table_name,
4649 crate::role::Verb::Update,
4650 )?,
4651 );
4652
4653 execute_update(
4654 pool,
4655 gql_ctx,
4656 schema_name,
4657 table_name,
4658 &type_name,
4659 operators,
4660 column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await,
4661 where_clause,
4662 relationships.as_ref(),
4663 names.as_ref(),
4664 )
4665 .await
4666 .map(|rows| {
4667 let count = rows.len();
4668 (rows, count)
4669 })?
4670 }
4671 MutationType::Delete | MutationType::DeleteByPk => {
4672 let where_clause = if mutation_type == MutationType::DeleteByPk {
4673 Some(pk_where_from_args(
4674 ctx,
4675 schema_name,
4676 table_name,
4677 pk_columns,
4678 names.as_ref(),
4679 )?)
4680 } else {
4681 ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4682 };
4683 let where_clause = and_predicates(
4684 where_clause,
4685 permission_filter(
4686 &gql_ctx.caller(),
4687 names.as_ref(),
4688 schema_name,
4689 table_name,
4690 crate::role::Verb::Delete,
4691 )?,
4692 );
4693
4694 execute_delete(
4695 pool,
4696 gql_ctx,
4697 schema_name,
4698 table_name,
4699 &type_name,
4700 where_clause,
4701 returning,
4702 relationships.as_ref(),
4703 names.as_ref(),
4704 max_rows,
4705 )
4706 .await
4707 .map(|rows| {
4708 let count = rows.len();
4709 (rows, count)
4710 })?
4711 }
4712 };
4713
4714 let is_delete = matches!(
4715 mutation_type,
4716 MutationType::Delete | MutationType::DeleteByPk
4717 );
4718 let result = match returning {
4719 Some(returning) if !result.is_empty() && !is_delete => {
4720 reread_returning(
4721 pool,
4722 gql_ctx,
4723 schema_name,
4724 table_name,
4725 &type_name,
4726 result,
4727 returning,
4728 relationships.as_ref(),
4729 names.as_ref(),
4730 max_rows,
4731 )
4732 .await?
4733 }
4734 _ => result,
4735 };
4736
4737 Ok(mutation_result(result, affected, by_key))
4738}
4739
4740async fn write_tx<'a>(
4748 gql_ctx: &'a GraphQLContext,
4749 pool: &PgPool,
4750) -> Result<
4751 tokio::sync::MutexGuard<'a, Option<sqlx::Transaction<'static, sqlx::Postgres>>>,
4752 async_graphql::Error,
4753> {
4754 let mut guard = gql_ctx.write.lock().await;
4755 if guard.is_none() {
4756 *guard = Some(begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?);
4757 }
4758 Ok(guard)
4759}
4760
4761async fn begin_with_session(
4767 pool: &PgPool,
4768 role: &str,
4769 settings: &[(String, String)],
4770) -> Result<sqlx::Transaction<'static, sqlx::Postgres>, async_graphql::Error> {
4771 let mut tx = pool.begin().await?;
4772
4773 if !settings.is_empty() {
4783 let calls = (0..settings.len())
4784 .map(|i| format!("set_config(${}, ${}, true)", i * 2 + 1, i * 2 + 2))
4785 .collect::<Vec<_>>()
4786 .join(", ");
4787 let sql = format!("SELECT {calls}");
4788 let mut query = sqlx::query_scalar::<_, String>(&sql);
4789 for (name, value) in settings {
4790 query = query.bind(name).bind(value);
4791 }
4792 query.fetch_optional(&mut *tx).await?;
4793 }
4794
4795 sqlx::query(&format!(
4796 "SET LOCAL ROLE {}",
4797 postrust_sql::escape_ident(role)
4798 ))
4799 .execute(&mut *tx)
4800 .await?;
4801 Ok(tx)
4802}
4803
4804fn row_json(expr: &str, column_types: &HashMap<String, String>) -> String {
4815 let mut shapes: Vec<(&String, &String)> = column_types
4816 .iter()
4817 .filter(|(_, pg_type)| matches!(pg_type.as_str(), "geometry" | "geography"))
4818 .collect();
4819 if shapes.is_empty() {
4820 return format!("row_to_json({})", expr);
4821 }
4822 shapes.sort();
4823
4824 let overrides: Vec<String> = shapes
4825 .iter()
4826 .map(|(name, _)| {
4827 format!(
4828 "'{}', ST_AsGeoJSON({}.{}, 9, 4)::jsonb",
4834 name.replace('\'', "''"),
4835 expr,
4836 postrust_sql::escape_ident(name)
4837 )
4838 })
4839 .collect();
4840 format!(
4841 "(to_jsonb({}) || jsonb_build_object({}))",
4842 expr,
4843 overrides.join(", ")
4844 )
4845}
4846
4847async fn execute_query_on(
4850 conn: &mut sqlx::PgConnection,
4851 sql: &str,
4852 params: &[serde_json::Value],
4853) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4854 use sqlx::Row;
4855
4856 trace!("Executing SQL: {}", sql);
4857
4858 let mut query = sqlx::query(sql);
4860 for param in params {
4861 query = bind_json_value(query, param);
4862 }
4863 let rows = query.fetch_all(&mut *conn).await.map_err(database_error)?;
4864
4865 let results: Vec<serde_json::Value> = rows
4868 .into_iter()
4869 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
4870 .collect();
4871
4872 Ok(results)
4873}
4874
4875fn check_geojson(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4888 fn refuse<T>(message: &str) -> Result<T, async_graphql::Error> {
4889 Err(coded_error("parse-failed", message))
4890 }
4891
4892 fn position(
4893 value: &serde_json::Value,
4894 ) -> Result<&Vec<serde_json::Value>, async_graphql::Error> {
4895 match value.as_array() {
4896 Some(items) if items.len() >= 2 && items.iter().all(|i| i.is_number()) => Ok(items),
4897 _ => refuse("A Position needs at least 2 elements"),
4898 }
4899 }
4900
4901 fn positions(
4902 value: Option<&serde_json::Value>,
4903 least: usize,
4904 what: &str,
4905 ) -> Result<Vec<Vec<serde_json::Value>>, async_graphql::Error> {
4906 let items = match value.and_then(|v| v.as_array()) {
4907 Some(items) => items,
4908 None => return refuse(&format!("A {} needs at least {} Positions", what, least)),
4909 };
4910 if items.len() < least {
4911 return refuse(&format!("A {} needs at least {} Positions", what, least));
4912 }
4913 items.iter().map(|item| position(item).cloned()).collect()
4914 }
4915
4916 fn ring(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4917 let points = positions(Some(value), 4, "LinearRing")?;
4918 match (points.first(), points.last()) {
4919 (Some(first), Some(last)) if first == last => Ok(()),
4920 _ => refuse("the first and last locations have to be equal for a LinearRing"),
4921 }
4922 }
4923
4924 fn polygon(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4925 let Some(rings) = value.as_array() else {
4926 return refuse("A LinearRing needs at least 4 Positions");
4927 };
4928 rings.iter().try_for_each(ring)
4929 }
4930
4931 let serde_json::Value::Object(map) = value else {
4932 return Ok(());
4933 };
4934 let Some(kind) = map.get("type").and_then(|v| v.as_str()) else {
4935 return Ok(());
4936 };
4937 let coordinates = map.get("coordinates");
4938 match kind {
4939 "Point" => {
4940 position(coordinates.unwrap_or(&serde_json::Value::Null))?;
4941 }
4942 "MultiPoint" => {
4943 positions(coordinates, 0, "MultiPoint")?;
4944 }
4945 "LineString" => {
4946 positions(coordinates, 2, "LineString")?;
4947 }
4948 "MultiLineString" => {
4949 for line in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4950 positions(Some(line), 2, "LineString")?;
4951 }
4952 }
4953 "Polygon" => polygon(coordinates.unwrap_or(&serde_json::Value::Null))?,
4954 "MultiPolygon" => {
4955 for one in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4956 polygon(one)?;
4957 }
4958 }
4959 "GeometryCollection" => {
4960 for geometry in map
4961 .get("geometries")
4962 .and_then(|v| v.as_array())
4963 .into_iter()
4964 .flatten()
4965 {
4966 check_geojson(geometry)?;
4967 }
4968 }
4969 other => return refuse(&format!("unexpected geometry type: {}", other)),
4970 }
4971 Ok(())
4972}
4973
4974fn computed_call(
4982 definition: &postrust_core::schema_cache::ComputedColumn,
4983 row: &str,
4984 row_by_name: &str,
4985) -> String {
4986 let function = format!(
4987 "{}.{}",
4988 postrust_sql::escape_ident(&definition.function.schema),
4989 postrust_sql::escape_ident(&definition.function.name)
4990 );
4991 match (&definition.session_argument, &definition.row_argument) {
4992 (Some(session), Some(row_argument)) => format!(
4993 "{}({} => {}, {} => {})",
4994 function,
4995 postrust_sql::escape_ident(row_argument),
4996 row_by_name,
4997 postrust_sql::escape_ident(session),
4998 SESSION_ARGUMENT
4999 ),
5000 _ => format!("{}({})", function, row),
5001 }
5002}
5003
5004const SESSION_ARGUMENT: &str = "coalesce(current_setting('hasura.session', true), '{}')::json";
5010
5011const UPDATE_OPERATORS: [&str; 7] = [
5014 "_set",
5015 "_inc",
5016 "_append",
5017 "_prepend",
5018 "_delete_key",
5019 "_delete_elem",
5020 "_delete_at_path",
5021];
5022
5023const READ_ROW: &str = "pgrst_src";
5029
5030const WRITTEN_ROW: &str = "pgrst_row";
5035
5036fn write_cast(column_types: &HashMap<String, String>, column: &str) -> String {
5044 match column_types.get(column) {
5045 Some(pg_type) => format!("::{}", pg_type),
5046 None => String::new(),
5047 }
5048}
5049
5050fn write_operand(
5057 column_types: &HashMap<String, String>,
5058 column: &str,
5059 value: &serde_json::Value,
5060) -> serde_json::Value {
5061 match column_types.get(column).map(String::as_str) {
5062 Some("json") | Some("jsonb") => match value {
5063 serde_json::Value::String(_)
5064 | serde_json::Value::Number(_)
5065 | serde_json::Value::Bool(_) => serde_json::Value::String(value.to_string()),
5066 other => other.clone(),
5067 },
5068 Some(pg_type) if is_array_type(pg_type) => match value {
5073 serde_json::Value::Array(_) => serde_json::Value::String(array_literal(value)),
5074 other => other.clone(),
5075 },
5076 _ => value.clone(),
5077 }
5078}
5079
5080fn is_array_type(pg_type: &str) -> bool {
5085 pg_type.ends_with("[]") || pg_type.starts_with('_')
5086}
5087
5088fn array_literal(value: &serde_json::Value) -> String {
5094 fn element(value: &serde_json::Value) -> String {
5095 match value {
5096 serde_json::Value::Null => "NULL".to_string(),
5097 serde_json::Value::Array(_) => array_literal(value),
5098 serde_json::Value::String(text) => {
5099 format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
5100 }
5101 other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
5102 }
5103 }
5104 let items = match value {
5105 serde_json::Value::Array(items) => items,
5106 other => return element(other),
5107 };
5108 format!(
5109 "{{{}}}",
5110 items.iter().map(element).collect::<Vec<_>>().join(",")
5111 )
5112}
5113
5114fn write_expression(
5125 column_types: &HashMap<String, String>,
5126 column: &str,
5127 value: &serde_json::Value,
5128 placeholder: &str,
5129) -> String {
5130 let pg_type = column_types.get(column).map(String::as_str);
5131 if value.is_object() && matches!(pg_type, Some("geometry") | Some("geography")) {
5132 return match pg_type {
5133 Some("geography") => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
5134 _ => format!("ST_GeomFromGeoJSON({})", placeholder),
5135 };
5136 }
5137 if pg_type == Some("money") && value.is_number() {
5143 return format!("{}::numeric::money", placeholder);
5144 }
5145 format!("{}{}", placeholder, write_cast(column_types, column))
5146}
5147
5148async fn column_types_of(
5150 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
5151 schema_name: &str,
5152 table_name: &str,
5153) -> HashMap<String, String> {
5154 let Ok(guard) = schema_cache.get().await else {
5155 return HashMap::new();
5156 };
5157 let Some(cache) = guard.as_ref() else {
5158 return HashMap::new();
5159 };
5160 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5161 match cache.get_table(&qi) {
5162 Some(table) => table
5163 .columns
5164 .values()
5165 .map(|c| (c.name.clone(), c.nominal_type.clone()))
5166 .collect(),
5167 None => HashMap::new(),
5168 }
5169}
5170
5171fn insert_row<'life>(
5189 conn: &'life mut sqlx::PgConnection,
5190 schema_name: &'life str,
5191 table_name: &'life str,
5192 object: serde_json::Map<String, serde_json::Value>,
5193 context: &'life InsertContext<'life>,
5194 written_count: &'life mut usize,
5195) -> std::pin::Pin<
5196 Box<
5197 dyn std::future::Future<Output = Result<serde_json::Value, async_graphql::Error>>
5198 + Send
5199 + 'life,
5200 >,
5201> {
5202 Box::pin(async move {
5203 use sqlx::Row;
5204
5205 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5206 let table = context.cache.get_table(&qi).ok_or_else(|| {
5207 async_graphql::Error::new(format!("unknown table \"{}\"", table_name))
5208 })?;
5209 let column_types: HashMap<String, String> = table
5210 .columns
5211 .values()
5212 .map(|c| (c.name.clone(), c.nominal_type.clone()))
5213 .collect();
5214
5215 let type_name = context
5217 .type_names
5218 .get(&(schema_name.to_string(), table_name.to_string()));
5219 let relationships: &[RelationshipField] = type_name
5220 .and_then(|name| context.relationships.get(name))
5221 .map(|r| r.as_slice())
5222 .unwrap_or(&[]);
5223
5224 let mut columns = serde_json::Map::new();
5225 type Nested<'r> = (
5226 &'r RelationshipField,
5227 serde_json::Value,
5228 Option<serde_json::Value>,
5229 );
5230 let mut to_one: Vec<Nested> = Vec::new();
5231 let mut to_many: Vec<Nested> = Vec::new();
5232
5233 for (key, value) in object {
5234 match relationships.iter().find(|r| r.name == key) {
5235 None => {
5236 columns.insert(
5239 table_column_for(context.names, table, &key).to_string(),
5240 value,
5241 );
5242 }
5243 Some(_) if value.is_null() => {
5244 continue;
5248 }
5249 Some(relationship) => {
5250 let (data, conflict) = match &value {
5254 serde_json::Value::Object(map) => (
5255 map.get("data").cloned().unwrap_or(value.clone()),
5256 map.get("on_conflict").cloned().filter(|v| !v.is_null()),
5257 ),
5258 other => (other.clone(), None),
5259 };
5260 if child_holds_the_key(&relationship.relationship) {
5267 to_many.push((relationship, data, conflict));
5268 } else {
5269 to_one.push((relationship, data, conflict));
5270 }
5271 }
5272 }
5273 }
5274
5275 for (column, value) in &columns {
5278 if matches!(
5279 column_types.get(column).map(String::as_str),
5280 Some("geometry") | Some("geography")
5281 ) {
5282 check_geojson(value).map_err(|error| {
5285 let field = context
5286 .names
5287 .column(schema_name, table_name, column)
5288 .unwrap_or(column);
5289 at_path(error, &format!("{}.{}", context.row, field))
5290 })?;
5291 }
5292 }
5293
5294 for (relationship, data, conflict) in to_one {
5297 let plan =
5298 postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5299 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5300 let serde_json::Value::Object(child) = data else {
5301 return Err(async_graphql::Error::new(format!(
5302 "\"{}\" takes an object to insert",
5303 relationship.name
5304 )));
5305 };
5306 for (local, _) in &plan.columns {
5310 if columns.get(local).is_some_and(|v| !v.is_null()) {
5311 return Err(at_path(
5312 validation_error(format!(
5313 "cannot insert object relationship \"{}\" as \"{}\" column \
5314 values are already determined",
5315 relationship.name, local
5316 )),
5317 &format!("{}.{}", context.row, relationship.name),
5318 ));
5319 }
5320 }
5321 let under = format!("{}.{}", context.row, relationship.name);
5324 let nested = InsertContext {
5325 on_conflict: conflict,
5326 cache: context.cache,
5327 relationships: context.relationships,
5328 type_names: context.type_names,
5329 names: context.names,
5330 caller: context.caller,
5331 row: format!("{}.data", under),
5332 objects: format!("{}.data", under),
5333 conflict: format!("{}.on_conflict", under),
5334 };
5335 let written = insert_row(
5336 conn,
5337 &plan.foreign_schema,
5338 &plan.foreign_table,
5339 child,
5340 &nested,
5341 written_count,
5342 )
5343 .await?;
5344 for (local, foreign) in &plan.columns {
5345 if let Some(value) = written.get(foreign) {
5346 columns.insert(local.clone(), value.clone());
5347 }
5348 }
5349 }
5350
5351 for (column, value) in crate::role::presets(
5357 &context.caller,
5358 context.names,
5359 schema_name,
5360 table_name,
5361 crate::role::Verb::Insert,
5362 )
5363 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?
5364 {
5365 columns.insert(column, value);
5366 }
5367
5368 let column_names: Vec<&str> = columns.keys().map(|k| k.as_str()).collect();
5374 let mut conflict_values: Vec<serde_json::Value> = Vec::new();
5375 let conflict_sql = match &context.on_conflict {
5376 Some(serde_json::Value::Object(spec)) => {
5377 let constraint = spec.get("constraint").and_then(|v| v.as_str());
5378 let Some(constraint) = constraint else {
5379 return Err(async_graphql::Error::new(
5380 "on_conflict needs a constraint to resolve against",
5381 ));
5382 };
5383 if !table
5384 .unique_constraints
5385 .iter()
5386 .any(|(name, _)| name == constraint)
5387 {
5388 return Err(async_graphql::Error::new(format!(
5389 "\"{}\" is not a unique constraint of \"{}\"",
5390 constraint, table_name
5391 )));
5392 }
5393 let updates: Vec<&str> = spec
5394 .get("update_columns")
5395 .and_then(|v| v.as_array())
5396 .map(|items| items.iter().filter_map(|i| i.as_str()).collect())
5397 .unwrap_or_default();
5398 if updates.contains(&PLACEHOLDER_COLUMN) {
5402 return Err(at_path(
5403 coded_error("validation-failed", "erroneous column name"),
5404 &context.conflict,
5405 ));
5406 }
5407
5408 if updates.is_empty() {
5409 format!(
5410 " ON CONFLICT ON CONSTRAINT {} DO NOTHING",
5411 postrust_sql::escape_ident(constraint)
5412 )
5413 } else {
5414 let preset = crate::role::presets(
5420 &context.caller,
5421 context.names,
5422 schema_name,
5423 table_name,
5424 crate::role::Verb::Update,
5425 )
5426 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5427
5428 let mut param_idx = column_names.len() + 1;
5432 let mut assignments: Vec<String> =
5433 Vec::with_capacity(preset.len() + updates.len());
5434 for (column, value) in &preset {
5435 assignments.push(format!(
5436 "{} = {}",
5437 postrust_sql::escape_ident(column),
5438 write_expression(
5439 &column_types,
5440 column,
5441 value,
5442 &format!("${}", param_idx)
5443 )
5444 ));
5445 conflict_values.push(write_operand(&column_types, column, value));
5446 param_idx += 1;
5447 }
5448 for field in &updates {
5449 let column = table_column_for(context.names, table, field);
5450 if preset.iter().any(|(written, _)| written == column) {
5451 continue;
5452 }
5453 assignments.push(format!(
5454 "{} = EXCLUDED.{}",
5455 postrust_sql::escape_ident(column),
5456 postrust_sql::escape_ident(column)
5457 ));
5458 }
5459 let granted = permission_filter(
5472 &context.caller,
5473 context.names,
5474 schema_name,
5475 table_name,
5476 crate::role::Verb::Update,
5477 )?;
5478 let requested = spec.get("where").filter(|f| !f.is_null()).cloned();
5479 let condition = match and_predicates(requested, granted) {
5480 Some(filter) => {
5481 let filter = &filter;
5482 let type_name = context
5483 .type_names
5484 .get(&(schema_name.to_string(), table_name.to_string()))
5485 .map(String::as_str)
5486 .unwrap_or(table_name);
5487 let scope = WhereScope::table(
5488 schema_name,
5489 table_name,
5490 type_name,
5491 context.names,
5492 )
5493 .under_alias(WRITTEN_ROW)
5494 .with_resolution(context.cache, context.relationships);
5495 let mut alias_counter = 0usize;
5496 build_condition(
5497 filter,
5498 &scope,
5499 &mut param_idx,
5500 &mut conflict_values,
5501 &mut alias_counter,
5502 )?
5503 .map(|sql| format!(" WHERE {}", sql))
5504 .unwrap_or_default()
5505 }
5506 None => String::new(),
5507 };
5508 format!(
5509 " ON CONFLICT ON CONSTRAINT {} DO UPDATE SET {}{}",
5510 postrust_sql::escape_ident(constraint),
5511 assignments.join(", "),
5512 condition
5513 )
5514 }
5515 }
5516 _ => String::new(),
5517 };
5518
5519 let qualified_table = postrust_sql::escape_ident(WRITTEN_ROW);
5525
5526 let check_scope = WhereScope::for_alias(
5531 schema_name,
5532 table_name,
5533 WRITTEN_ROW,
5534 context
5535 .type_names
5536 .get(&(schema_name.to_string(), table_name.to_string()))
5537 .map(String::as_str)
5538 .unwrap_or(table_name),
5539 context.cache,
5540 context.relationships,
5541 context.names,
5542 );
5543 let (check_sql, check_values) = {
5544 let check = crate::role::write_check(
5545 &context.caller,
5546 context.names,
5547 schema_name,
5548 table_name,
5549 crate::role::Verb::Insert,
5550 )
5551 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5552 match check {
5553 None => (String::new(), Vec::new()),
5554 Some(check) => {
5555 let mut values = Vec::new();
5556 let mut param_idx = column_names.len() + conflict_values.len() + 1;
5557 let mut alias_counter = 0usize;
5558 match build_condition(
5559 &check,
5560 &check_scope,
5561 &mut param_idx,
5562 &mut values,
5563 &mut alias_counter,
5564 )? {
5565 None => (String::new(), Vec::new()),
5566 Some(sql) => (
5567 format!(
5568 ", ({}) AS {}",
5569 sql,
5570 postrust_sql::escape_ident(CHECK_COLUMN)
5571 ),
5572 values,
5573 ),
5574 }
5575 }
5576 }
5577 };
5578
5579 let names = column_names;
5580 let written = if names.is_empty() {
5581 let sql = format!(
5584 "INSERT INTO {}.{} AS {} DEFAULT VALUES{} RETURNING {}{}",
5585 postrust_sql::escape_ident(schema_name),
5586 postrust_sql::escape_ident(table_name),
5587 qualified_table,
5588 conflict_sql,
5589 row_json(&qualified_table, &column_types),
5590 check_sql
5591 );
5592 let mut query = sqlx::query(&sql);
5593 for value in &conflict_values {
5594 query = bind_json_value(query, value);
5595 }
5596 for value in &check_values {
5597 query = bind_json_value(query, value);
5598 }
5599 query
5600 .fetch_optional(&mut *conn)
5601 .await
5602 .map_err(|error| at_path(database_error(error), &context.objects))?
5605 } else {
5606 let placeholders: Vec<String> = names
5607 .iter()
5608 .enumerate()
5609 .map(|(i, column)| {
5610 let placeholder = format!("${}", i + 1);
5611 match columns.get(*column) {
5612 Some(value) => write_expression(&column_types, column, value, &placeholder),
5613 None => placeholder,
5614 }
5615 })
5616 .collect();
5617 let sql = format!(
5618 "INSERT INTO {}.{} AS {} ({}) VALUES ({}){} RETURNING {}{}",
5619 postrust_sql::escape_ident(schema_name),
5620 postrust_sql::escape_ident(table_name),
5621 qualified_table,
5622 names
5623 .iter()
5624 .map(|c| postrust_sql::escape_ident(c))
5625 .collect::<Vec<_>>()
5626 .join(", "),
5627 placeholders.join(", "),
5628 conflict_sql,
5629 row_json(&qualified_table, &column_types),
5630 check_sql
5631 );
5632 trace!("Executing INSERT SQL: {}", sql);
5633 let mut query = sqlx::query(&sql);
5634 for column in &names {
5635 if let Some(value) = columns.get(*column) {
5636 query = bind_json_value(query, &write_operand(&column_types, column, value));
5637 }
5638 }
5639 for value in &conflict_values {
5640 query = bind_json_value(query, value);
5641 }
5642 for value in &check_values {
5643 query = bind_json_value(query, value);
5644 }
5645 query
5646 .fetch_optional(&mut *conn)
5647 .await
5648 .map_err(|error| at_path(database_error(error), &context.objects))?
5651 };
5652
5653 let Some(written) = written else {
5656 return Ok(serde_json::Value::Null);
5657 };
5658 refuse_unchecked_rows(std::slice::from_ref(&written), !check_sql.is_empty())
5659 .map_err(|error| at_path(error, &context.objects))?;
5660 let row: serde_json::Value = written.try_get(0).unwrap_or(serde_json::Value::Null);
5661 *written_count += 1;
5662
5663 for (relationship, data, conflict) in to_many {
5665 let plan =
5666 postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5667 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5668 let children = match data {
5669 serde_json::Value::Array(items) => items,
5670 serde_json::Value::Object(map) => vec![serde_json::Value::Object(map)],
5671 serde_json::Value::Null => Vec::new(),
5672 _ => {
5673 return Err(async_graphql::Error::new(format!(
5674 "\"{}\" takes objects to insert",
5675 relationship.name
5676 )))
5677 }
5678 };
5679 for (child_index, child) in children.into_iter().enumerate() {
5680 let serde_json::Value::Object(mut child) = child else {
5681 continue;
5682 };
5683 for (local, foreign) in &plan.columns {
5684 if child.get(foreign).is_some_and(|v| !v.is_null()) {
5688 return Err(at_path(
5689 validation_error(format!(
5690 "cannot insert \"{}\" columns as their values are already \
5691 being determined by parent insert",
5692 foreign
5693 )),
5694 &format!(
5695 "{}.{}{}",
5696 context.row,
5697 relationship.name,
5698 match relationship.is_list {
5699 true => format!(".data[{}]", child_index),
5700 false => ".data".to_string(),
5701 }
5702 ),
5703 ));
5704 }
5705 if let Some(value) = row.get(local) {
5706 child.insert(foreign.clone(), value.clone());
5707 }
5708 }
5709 let at = match relationship.is_list {
5715 true => format!(".data[{}]", child_index),
5716 false => ".data".to_string(),
5717 };
5718 let under = format!("{}.{}", context.row, relationship.name);
5719 let nested = InsertContext {
5720 on_conflict: conflict.clone(),
5721 cache: context.cache,
5722 relationships: context.relationships,
5723 type_names: context.type_names,
5724 names: context.names,
5725 caller: context.caller,
5726 row: format!("{}{}", under, at),
5727 objects: format!("{}.data", under),
5728 conflict: format!("{}.on_conflict", under),
5729 };
5730 insert_row(
5731 conn,
5732 &plan.foreign_schema,
5733 &plan.foreign_table,
5734 child,
5735 &nested,
5736 written_count,
5737 )
5738 .await?;
5739 }
5740 }
5741
5742 Ok(row)
5743 })
5744}
5745
5746#[derive(Clone)]
5752struct InsertContext<'a> {
5753 on_conflict: Option<serde_json::Value>,
5756 cache: &'a SchemaCache,
5757 relationships: &'a HashMap<String, Vec<RelationshipField>>,
5758 type_names: &'a HashMap<(String, String), String>,
5762 names: &'a crate::names::NameOverrides,
5765 caller: crate::role::Caller<'a>,
5769 row: String,
5774 objects: String,
5778 conflict: String,
5780}
5781
5782#[allow(clippy::too_many_arguments)]
5795async fn reread_returning(
5796 pool: &PgPool,
5797 gql_ctx: &GraphQLContext,
5798 schema_name: &str,
5799 table_name: &str,
5800 type_name: &str,
5801 rows: Vec<Value>,
5802 returning: async_graphql::SelectionField<'_>,
5803 relationships: &HashMap<String, Vec<RelationshipField>>,
5804 names: &crate::names::NameOverrides,
5805 max_rows: Option<i64>,
5806) -> Result<Vec<Value>, async_graphql::Error> {
5807 let guard = gql_ctx
5808 .schema_cache
5809 .get()
5810 .await
5811 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5812 let Some(cache) = guard.as_ref() else {
5813 return Ok(rows);
5814 };
5815 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5816 let Some(table) = cache.get_table(&qi) else {
5817 return Ok(rows);
5818 };
5819 if table.pk_cols.is_empty() {
5820 return Ok(rows);
5822 }
5823
5824 let mut param_idx = 1usize;
5825 let mut values: Vec<serde_json::Value> = Vec::new();
5826 let mut alias_counter = 0usize;
5827 let embeds = build_embed_expressions(
5828 &gql_ctx.caller(),
5829 cache,
5830 relationships,
5831 type_name,
5832 "src",
5833 returning.selection_set(),
5834 max_rows,
5835 &mut alias_counter,
5836 &mut param_idx,
5837 &mut values,
5838 names,
5839 )?;
5840 let computed = computed_projections(
5841 table,
5842 returning.selection_set(),
5843 "src",
5844 names,
5845 cache,
5846 &mut param_idx,
5847 &mut values,
5848 )?;
5849 let renames = names.renames_columns(&table.schema, &table.name);
5853 if embeds.is_empty() && computed.is_empty() && !renames {
5854 return Ok(rows);
5855 }
5856
5857 let mut conditions = Vec::with_capacity(rows.len());
5859 for row in &rows {
5860 let mut parts = Vec::with_capacity(table.pk_cols.len());
5861 for column in &table.pk_cols {
5862 let Value::Object(map) = row else {
5863 return Ok(rows);
5864 };
5865 let Some(value) = map.get(&async_graphql::Name::new(column)) else {
5866 return Ok(rows);
5867 };
5868 parts.push(format!(
5869 "{} = ${}",
5870 postrust_sql::escape_ident(column),
5871 param_idx
5872 ));
5873 values.push(value_to_json(value));
5874 param_idx += 1;
5875 }
5876 conditions.push(format!("({})", parts.join(" AND ")));
5877 }
5878 if conditions.is_empty() {
5879 return Ok(rows);
5880 }
5881
5882 let mut projection =
5883 rename_projection(table, "src", names).unwrap_or_else(|| "src.*".to_string());
5884 for expression in &computed {
5885 projection.push_str(", ");
5886 projection.push_str(expression);
5887 }
5888 for (name, expression) in &embeds {
5889 projection.push_str(", ");
5890 projection.push_str(expression);
5891 projection.push_str(" AS ");
5892 projection.push_str(&postrust_sql::escape_ident(name));
5893 }
5894
5895 let sql = format!(
5896 "SELECT {} FROM (SELECT {} FROM (SELECT * FROM {}.{} WHERE {}) AS src) AS pgrst_r",
5897 row_json("pgrst_r", &exposed_column_types(table, names)),
5898 projection,
5899 postrust_sql::escape_ident(schema_name),
5900 postrust_sql::escape_ident(table_name),
5901 conditions.join(" OR ")
5902 );
5903
5904 let mut guard = write_tx(gql_ctx, pool).await?;
5907 let conn = guard.as_mut().expect("write_tx opens one");
5908 let reread = execute_query_on(conn, &sql, &values).await?;
5909 drop(guard);
5910
5911 if reread.is_empty() {
5912 return Ok(rows);
5913 }
5914 Ok(reread.into_iter().map(json_to_value).collect())
5915}
5916
5917async fn execute_insert(
5919 pool: &PgPool,
5920 gql_ctx: &GraphQLContext,
5921 schema_name: &str,
5922 table_name: &str,
5923 objects: serde_json::Value,
5924 context: &InsertContext<'_>,
5925 single: bool,
5929) -> Result<(Vec<Value>, usize), async_graphql::Error> {
5930 trace!("Insert mutation for {}: {:?}", table_name, objects);
5931
5932 let objects_array = match objects {
5934 serde_json::Value::Array(arr) => arr,
5935 serde_json::Value::Object(obj) => vec![serde_json::Value::Object(obj)],
5936 _ => {
5937 return Err(async_graphql::Error::new(
5938 "objects must be an array or object",
5939 ))
5940 }
5941 };
5942
5943 if objects_array.is_empty() {
5944 return Err(async_graphql::Error::new("objects cannot be empty"));
5945 }
5946
5947 let mut guard = write_tx(gql_ctx, pool).await?;
5948 let conn = guard.as_mut().expect("write_tx opens one");
5949
5950 let mut inserted: Vec<Value> = Vec::new();
5951 let mut written = 0usize;
5952
5953 for (index, object) in objects_array.into_iter().enumerate() {
5954 let serde_json::Value::Object(map) = object else {
5955 return Err(async_graphql::Error::new(
5956 "each object to insert is an object",
5957 ));
5958 };
5959 let context = InsertContext {
5962 row: match single {
5963 true => context.objects.clone(),
5964 false => format!("{}[{}]", context.objects, index),
5965 },
5966 ..context.clone()
5967 };
5968 let row = insert_row(conn, schema_name, table_name, map, &context, &mut written).await?;
5969 if !row.is_null() {
5973 inserted.push(json_to_value(row));
5974 }
5975 }
5976
5977 Ok((inserted, written))
5978}
5979
5980fn mutation_result<'a>(rows: Vec<Value>, affected: usize, by_key: bool) -> Option<FieldValue<'a>> {
5992 if by_key {
5993 return rows.into_iter().next().map(FieldValue::value);
5994 }
5995 let mut response = async_graphql::indexmap::IndexMap::new();
5996 response.insert(
5997 async_graphql::Name::new("affected_rows"),
5998 Value::from(affected),
5999 );
6000 response.insert(async_graphql::Name::new("returning"), Value::List(rows));
6001 Some(FieldValue::value(Value::Object(response)))
6002}
6003
6004fn bind_json_value<'q>(
6006 query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
6007 value: &serde_json::Value,
6008) -> sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments> {
6009 match value {
6010 serde_json::Value::Null => query.bind(None::<String>),
6011 serde_json::Value::Bool(b) => query.bind(*b),
6012 serde_json::Value::Number(n) => {
6013 if let Some(i) = n.as_i64() {
6014 query.bind(i)
6015 } else if let Some(f) = n.as_f64() {
6016 query.bind(f)
6017 } else {
6018 query.bind(n.to_string())
6019 }
6020 }
6021 serde_json::Value::String(s) => query.bind(s.clone()),
6022 _ => query.bind(value.to_string()),
6023 }
6024}
6025
6026#[allow(clippy::too_many_arguments)] async fn execute_update(
6029 pool: &PgPool,
6030 gql_ctx: &GraphQLContext,
6031 schema_name: &str,
6032 table_name: &str,
6033 type_name: &str,
6034 operators: Vec<(&'static str, serde_json::Value)>,
6035 column_types: HashMap<String, String>,
6036 where_clause: Option<serde_json::Value>,
6037 relationships: &HashMap<String, Vec<RelationshipField>>,
6038 names: &crate::names::NameOverrides,
6039) -> Result<Vec<Value>, async_graphql::Error> {
6040 use sqlx::Row;
6041
6042 trace!("Update mutation for {}: {:?}", table_name, operators);
6043
6044 let mut set_parts: Vec<String> = Vec::new();
6049 let mut set_values: Vec<serde_json::Value> = Vec::new();
6050 let mut param_idx = 1;
6051 let mut written: HashSet<String> = HashSet::new();
6052
6053 let preset = crate::role::presets(
6063 &gql_ctx.caller(),
6064 names,
6065 schema_name,
6066 table_name,
6067 crate::role::Verb::Update,
6068 )
6069 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6070 let preset_columns: HashSet<&str> = preset.iter().map(|(name, _)| name.as_str()).collect();
6071 for (column, value) in &preset {
6072 let placeholder =
6073 write_expression(&column_types, column, value, &format!("${}", param_idx));
6074 set_parts.push(format!(
6075 "{} = {}",
6076 postrust_sql::escape_ident(column),
6077 placeholder
6078 ));
6079 set_values.push(write_operand(&column_types, column, value));
6080 param_idx += 1;
6081 }
6082
6083 for (operator, payload) in &operators {
6084 let serde_json::Value::Object(map) = payload else {
6085 return Err(async_graphql::Error::new(format!(
6086 "\"{}\" takes an object mapping columns to values",
6087 operator
6088 )));
6089 };
6090 for (field, value) in map {
6091 let column = &names
6092 .column_source(schema_name, table_name, field)
6093 .unwrap_or(field)
6094 .to_string();
6095 if preset_columns.contains(column.as_str()) {
6098 continue;
6099 }
6100 if !written.insert(column.clone()) {
6101 return Err(async_graphql::Error::new(format!(
6102 "\"{}\" is written twice in one update; a column may be \
6103 changed by one operator at a time",
6104 column
6105 )));
6106 }
6107 let quoted = postrust_sql::escape_ident(column);
6108 let placeholder = match *operator {
6117 "_set" | "_inc" => {
6118 write_expression(&column_types, column, value, &format!("${}", param_idx))
6119 }
6120 _ => format!("${}", param_idx),
6121 };
6122 let assignment = match *operator {
6125 "_set" => format!("{} = {}", quoted, placeholder),
6126 "_inc" => format!("{} = {} + {}", quoted, quoted, placeholder),
6127 "_append" => format!("{} = {} || {}::jsonb", quoted, quoted, placeholder),
6128 "_prepend" => format!("{} = {}::jsonb || {}", quoted, placeholder, quoted),
6129 "_delete_key" => format!("{} = {} - {}::text", quoted, quoted, placeholder),
6130 "_delete_elem" => format!("{} = {} - {}::int", quoted, quoted, placeholder),
6131 "_delete_at_path" => {
6137 let Some(steps) = value.as_array() else {
6138 return Err(async_graphql::Error::new(format!(
6139 "\"_delete_at_path\" on \"{}\" takes a list of keys",
6140 column
6141 )));
6142 };
6143 let path = sql_array(steps, "text[]", &mut param_idx, &mut set_values);
6144 set_parts.push(format!("{} = {} #- {}", quoted, quoted, path));
6145 continue;
6146 }
6147 other => {
6148 return Err(async_graphql::Error::new(format!(
6149 "unsupported update operator \"{}\"",
6150 other
6151 )))
6152 }
6153 };
6154 if *operator == "_set"
6155 && matches!(
6156 column_types.get(column).map(String::as_str),
6157 Some("geometry") | Some("geography")
6158 )
6159 {
6160 check_geojson(value)?;
6161 }
6162 set_parts.push(assignment);
6163 set_values.push(match *operator {
6164 "_set" => write_operand(&column_types, column, value),
6165 _ => value.clone(),
6166 });
6167 param_idx += 1;
6168 }
6169 }
6170
6171 if set_parts.is_empty() {
6176 return Ok(Vec::new());
6177 }
6178
6179 let guard = gql_ctx
6183 .schema_cache
6184 .get()
6185 .await
6186 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6187 let cache = guard
6188 .as_ref()
6189 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6190 let scope = WhereScope::table(schema_name, table_name, type_name, names)
6191 .under_alias(WRITTEN_ROW)
6192 .with_resolution(cache, relationships);
6193 let (where_sql, where_values) = build_where_clause(where_clause.as_ref(), param_idx, &scope)?;
6194
6195 if where_sql.is_empty() {
6198 return Err(async_graphql::Error::new(format!(
6199 "update on \"{}\" requires a `where` argument with at least one \
6200 recognised condition; refusing to update every row",
6201 table_name
6202 )));
6203 }
6204
6205 let (check_sql, check_values) = permission_check_sql(
6210 gql_ctx,
6211 names,
6212 schema_name,
6213 table_name,
6214 crate::role::Verb::Update,
6215 &scope,
6216 param_idx + where_values.len(),
6217 )?;
6218
6219 let sql = format!(
6220 "UPDATE {}.{} AS {} SET {} {} RETURNING {}{}",
6221 postrust_sql::escape_ident(schema_name),
6222 postrust_sql::escape_ident(table_name),
6223 postrust_sql::escape_ident(WRITTEN_ROW),
6224 set_parts.join(", "),
6225 where_sql,
6226 row_json(&postrust_sql::escape_ident(WRITTEN_ROW), &column_types),
6227 check_sql
6228 );
6229
6230 trace!("Executing UPDATE SQL: {}", sql);
6231
6232 let mut query = sqlx::query(&sql);
6234
6235 for val in &set_values {
6237 query = bind_json_value(query, val);
6238 }
6239
6240 for val in &where_values {
6242 query = bind_json_value(query, val);
6243 }
6244
6245 for val in &check_values {
6247 query = bind_json_value(query, val);
6248 }
6249
6250 let mut guard = write_tx(gql_ctx, pool).await?;
6251 let conn = guard.as_mut().expect("write_tx opens one");
6252 let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6253
6254 refuse_unchecked_rows(&rows, !check_sql.is_empty())?;
6255
6256 let updated: Vec<Value> = rows
6257 .iter()
6258 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6259 .map(json_to_value)
6260 .collect();
6261
6262 Ok(updated)
6263}
6264
6265fn permission_check_sql(
6272 gql_ctx: &GraphQLContext,
6273 names: &crate::names::NameOverrides,
6274 schema_name: &str,
6275 table_name: &str,
6276 verb: crate::role::Verb,
6277 scope: &WhereScope<'_>,
6278 start_param_idx: usize,
6279) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6280 let check = crate::role::write_check(&gql_ctx.caller(), names, schema_name, table_name, verb)
6281 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6282 let Some(check) = check else {
6283 return Ok((String::new(), Vec::new()));
6284 };
6285
6286 let mut values = Vec::new();
6287 let mut param_idx = start_param_idx;
6288 let mut alias_counter = 0usize;
6289 let condition = build_condition(
6290 &check,
6291 scope,
6292 &mut param_idx,
6293 &mut values,
6294 &mut alias_counter,
6295 )?;
6296 Ok(match condition {
6297 None => (String::new(), Vec::new()),
6300 Some(sql) => (
6301 format!(
6302 ", ({}) AS {}",
6303 sql,
6304 postrust_sql::escape_ident(CHECK_COLUMN)
6305 ),
6306 values,
6307 ),
6308 })
6309}
6310
6311const CHECK_COLUMN: &str = "pgrst_permission_check";
6313
6314fn refuse_unchecked_rows(
6321 rows: &[sqlx::postgres::PgRow],
6322 checked: bool,
6323) -> Result<(), async_graphql::Error> {
6324 if !checked {
6325 return Ok(());
6326 }
6327 use sqlx::Row;
6328 let all_passed = rows
6329 .iter()
6330 .all(|row| row.try_get::<bool, _>(CHECK_COLUMN).unwrap_or(false));
6331 match all_passed {
6332 true => Ok(()),
6333 false => Err(coded_error("permission-error", crate::role::CHECK_FAILED)),
6334 }
6335}
6336
6337#[allow(clippy::too_many_arguments)] async fn execute_delete(
6340 pool: &PgPool,
6341 gql_ctx: &GraphQLContext,
6342 schema_name: &str,
6343 table_name: &str,
6344 type_name: &str,
6345 where_clause: Option<serde_json::Value>,
6346 returning: Option<async_graphql::SelectionField<'_>>,
6347 relationships: &HashMap<String, Vec<RelationshipField>>,
6348 names: &crate::names::NameOverrides,
6349 max_rows: Option<i64>,
6350) -> Result<Vec<Value>, async_graphql::Error> {
6351 use sqlx::Row;
6352
6353 trace!("Delete mutation for {}", table_name);
6354
6355 let guard = gql_ctx
6356 .schema_cache
6357 .get()
6358 .await
6359 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6360 let cache = guard
6361 .as_ref()
6362 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6363 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
6364 let column_types = cache
6365 .get_table(&qi)
6366 .map(|table| exposed_column_types(table, names))
6367 .unwrap_or_default();
6368
6369 let scope = WhereScope::table(schema_name, table_name, type_name, names)
6373 .under_alias(WRITTEN_ROW)
6374 .with_resolution(cache, relationships);
6375 let (where_sql, mut values) = build_where_clause(where_clause.as_ref(), 1, &scope)?;
6376
6377 if where_sql.is_empty() {
6380 return Err(async_graphql::Error::new(format!(
6381 "delete on \"{}\" requires a `where` argument with at least one \
6382 recognised condition; refusing to delete every row",
6383 table_name
6384 )));
6385 }
6386
6387 let mut projection = String::new();
6393 let renamed = cache
6394 .get_table(&qi)
6395 .and_then(|table| rename_projection(table, "src", names));
6396 if let Some(returning) = returning {
6397 let mut param_idx = values.len() + 1;
6398 let mut alias_counter = 0usize;
6399 let embeds = build_embed_expressions(
6400 &gql_ctx.caller(),
6401 cache,
6402 relationships,
6403 type_name,
6404 "src",
6405 returning.selection_set(),
6406 max_rows,
6407 &mut alias_counter,
6408 &mut param_idx,
6409 &mut values,
6410 names,
6411 )?;
6412 let computed = match cache.get_table(&qi) {
6413 Some(table) => computed_projections(
6414 table,
6415 returning.selection_set(),
6416 "src",
6417 names,
6418 cache,
6419 &mut param_idx,
6420 &mut values,
6421 )?,
6422 None => Vec::new(),
6423 };
6424 for expression in &computed {
6425 projection.push_str(", ");
6426 projection.push_str(expression);
6427 }
6428 for (name, expression) in &embeds {
6429 projection.push_str(", ");
6430 projection.push_str(expression);
6431 projection.push_str(" AS ");
6432 projection.push_str(&postrust_sql::escape_ident(name));
6433 }
6434 }
6435
6436 let written = postrust_sql::escape_ident(WRITTEN_ROW);
6437 let sql = if projection.is_empty() && renamed.is_none() {
6438 format!(
6439 "DELETE FROM {}.{} AS {} {} RETURNING {}",
6440 postrust_sql::escape_ident(schema_name),
6441 postrust_sql::escape_ident(table_name),
6442 written,
6443 where_sql,
6444 row_json(&written, &column_types)
6445 )
6446 } else {
6447 format!(
6448 "WITH pgrst_deleted AS (DELETE FROM {}.{} AS {} {} RETURNING {}.*) \
6449 SELECT {} FROM (SELECT {}{} FROM pgrst_deleted AS src) AS pgrst_r",
6450 postrust_sql::escape_ident(schema_name),
6451 postrust_sql::escape_ident(table_name),
6452 written,
6453 where_sql,
6454 written,
6455 row_json("pgrst_r", &column_types),
6456 renamed.unwrap_or_else(|| "src.*".to_string()),
6457 projection
6458 )
6459 };
6460
6461 trace!("Executing DELETE SQL: {}", sql);
6462
6463 let mut query = sqlx::query(&sql);
6464 for val in &values {
6465 query = bind_json_value(query, val);
6466 }
6467
6468 let mut guard = write_tx(gql_ctx, pool).await?;
6469 let conn = guard.as_mut().expect("write_tx opens one");
6470 let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6471
6472 let deleted: Vec<Value> = rows
6473 .iter()
6474 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6475 .map(json_to_value)
6476 .collect();
6477
6478 Ok(deleted)
6479}
6480
6481fn permission_predicate(
6489 caller: &crate::role::Caller<'_>,
6490 names: &crate::names::NameOverrides,
6491 schema: &str,
6492 table: &str,
6493) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6494 permission_filter(caller, names, schema, table, crate::role::Verb::Select)
6495}
6496
6497fn permission_filter(
6499 caller: &crate::role::Caller<'_>,
6500 names: &crate::names::NameOverrides,
6501 schema: &str,
6502 table: &str,
6503 verb: crate::role::Verb,
6504) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6505 crate::role::row_filter(caller, names, schema, table, verb)
6506 .map(|filter| filter.map(|predicate| serde_json::json!({ PERMISSION: predicate })))
6507 .map_err(|fault| coded_error(fault.code(), fault.to_string()))
6508}
6509
6510const PERMISSION: &str = "_permission";
6531
6532fn permission_limit(
6538 caller: &crate::role::Caller<'_>,
6539 names: &crate::names::NameOverrides,
6540 schema: &str,
6541 table: &str,
6542) -> Option<i64> {
6543 crate::role::read_limit(caller, names, schema, table)
6544}
6545
6546fn and_predicates(
6553 request: Option<serde_json::Value>,
6554 permission: Option<serde_json::Value>,
6555) -> Option<serde_json::Value> {
6556 match (request, permission) {
6557 (None, None) => None,
6558 (Some(only), None) | (None, Some(only)) => Some(only),
6559 (Some(request), Some(permission)) => {
6560 Some(serde_json::json!({ "_and": [request, permission] }))
6561 }
6562 }
6563}
6564
6565fn build_where_clause(
6575 where_value: Option<&serde_json::Value>,
6576 start_param_idx: usize,
6577 scope: &WhereScope<'_>,
6578) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6579 let mut values: Vec<serde_json::Value> = Vec::new();
6580 let mut param_idx = start_param_idx;
6581 let mut alias_counter = 0usize;
6582
6583 let condition = match where_value {
6584 Some(value) => build_condition(
6585 value,
6586 scope,
6587 &mut param_idx,
6588 &mut values,
6589 &mut alias_counter,
6590 )?,
6591 None => None,
6592 };
6593
6594 Ok(match condition {
6595 Some(sql) => (format!("WHERE {}", sql), values),
6596 None => (String::new(), values),
6597 })
6598}
6599
6600pub struct WhereScope<'a> {
6612 qualified: postrust_core::api_request::QualifiedIdentifier,
6614 sql_ref: String,
6616 row_ref: String,
6620 type_name: String,
6622 root_ref: String,
6630 root_qualified: postrust_core::api_request::QualifiedIdentifier,
6631 names: &'a crate::names::NameOverrides,
6634 resolution: Option<WhereResolution<'a>>,
6635 caller: Option<crate::role::Caller<'a>>,
6642}
6643
6644struct WhereResolution<'a> {
6646 cache: &'a SchemaCache,
6647 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6648}
6649
6650impl<'a> WhereScope<'a> {
6651 pub fn table(
6653 schema: &str,
6654 table: &str,
6655 type_name: &str,
6656 names: &'a crate::names::NameOverrides,
6657 ) -> Self {
6658 Self {
6659 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6660 sql_ref: format!(
6661 "{}.{}",
6662 postrust_sql::escape_ident(schema),
6663 postrust_sql::escape_ident(table)
6664 ),
6665 row_ref: postrust_sql::escape_ident(table),
6666 type_name: type_name.to_string(),
6667 root_ref: format!(
6668 "{}.{}",
6669 postrust_sql::escape_ident(schema),
6670 postrust_sql::escape_ident(table)
6671 ),
6672 root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6673 names,
6674 resolution: None,
6675 caller: None,
6676 }
6677 }
6678
6679 fn under_alias(mut self, alias: &str) -> Self {
6687 if self.root_ref == self.sql_ref {
6690 self.root_ref = postrust_sql::escape_ident(alias);
6691 }
6692 self.sql_ref = postrust_sql::escape_ident(alias);
6693 self.row_ref = postrust_sql::escape_ident(alias);
6694 self
6695 }
6696
6697 fn with_resolution(
6699 mut self,
6700 cache: &'a SchemaCache,
6701 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6702 ) -> Self {
6703 self.resolution = Some(WhereResolution {
6704 cache,
6705 relationships,
6706 });
6707 self
6708 }
6709
6710 #[allow(clippy::too_many_arguments)]
6712 fn for_alias(
6713 schema: &str,
6714 table: &str,
6715 alias: &str,
6716 type_name: &str,
6717 cache: &'a SchemaCache,
6718 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6719 names: &'a crate::names::NameOverrides,
6720 ) -> Self {
6721 Self {
6722 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6723 sql_ref: postrust_sql::escape_ident(alias),
6724 row_ref: postrust_sql::escape_ident(alias),
6725 type_name: type_name.to_string(),
6726 root_ref: postrust_sql::escape_ident(alias),
6727 root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6728 names,
6729 resolution: Some(WhereResolution {
6730 cache,
6731 relationships,
6732 }),
6733 caller: None,
6734 }
6735 }
6736
6737 fn aliased(
6744 schema: &str,
6745 table: &str,
6746 alias: &str,
6747 type_name: &str,
6748 from: &WhereScope<'a>,
6749 ) -> Self {
6750 Self {
6751 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6752 sql_ref: postrust_sql::escape_ident(alias),
6753 row_ref: postrust_sql::escape_ident(alias),
6754 type_name: type_name.to_string(),
6755 root_ref: from.root_ref.clone(),
6758 root_qualified: from.root_qualified.clone(),
6759 names: from.names,
6760 resolution: from.resolution.as_ref().map(|r| WhereResolution {
6761 cache: r.cache,
6762 relationships: r.relationships,
6763 }),
6764 caller: from.caller,
6768 }
6769 }
6770
6771 pub fn for_caller(mut self, caller: crate::role::Caller<'a>) -> Self {
6773 self.caller = Some(caller);
6774 self
6775 }
6776
6777 fn as_system(&self) -> WhereScope<'a> {
6780 WhereScope {
6781 qualified: self.qualified.clone(),
6782 sql_ref: self.sql_ref.clone(),
6783 row_ref: self.row_ref.clone(),
6784 type_name: self.type_name.clone(),
6785 root_ref: self.sql_ref.clone(),
6792 root_qualified: self.qualified.clone(),
6793 names: self.names,
6794 resolution: self.resolution.as_ref().map(|r| WhereResolution {
6795 cache: r.cache,
6796 relationships: r.relationships,
6797 }),
6798 caller: None,
6799 }
6800 }
6801
6802 fn column(&self, name: &str) -> String {
6810 let column = self.column_name(name);
6811 let plain = format!("{}.{}", self.sql_ref, postrust_sql::escape_ident(column));
6812 let Some(cache) = self.resolution.as_ref().map(|r| r.cache) else {
6813 return plain;
6814 };
6815 let Some(table) = cache.get_table(&self.qualified) else {
6816 return plain;
6817 };
6818 if table.get_column(column).is_some() {
6819 return plain;
6820 }
6821 let function = self
6822 .names
6823 .computed_source(&table.schema, &table.name, name)
6824 .unwrap_or(name);
6825 match table.get_computed_column(function) {
6826 Some(definition) => {
6827 computed_call(definition, &format!("{}.*", self.row_ref), &self.row_ref)
6828 }
6829 None => plain,
6830 }
6831 }
6832
6833 fn column_name<'n>(&'n self, name: &'n str) -> &'n str {
6835 self.names
6836 .column_source(&self.qualified.schema, &self.qualified.name, name)
6837 .unwrap_or(name)
6838 }
6839
6840 fn column_type(&self, name: &str) -> Option<String> {
6844 let cache = self.resolution.as_ref()?.cache;
6845 let table = cache.get_table(&self.qualified)?;
6846 table
6847 .get_column(self.column_name(name))
6848 .map(|c| c.nominal_type.clone())
6849 }
6850
6851 fn other_column(
6865 &self,
6866 operand: &serde_json::Value,
6867 op: &str,
6868 ) -> Result<String, async_graphql::Error> {
6869 use serde_json::Value;
6870
6871 let refused = |what: &str| {
6872 async_graphql::Error::new(format!(
6873 "the \"{}\" comparison takes the name of a column, {}",
6874 op, what
6875 ))
6876 };
6877 let (against, qualified, name) = match operand {
6878 Value::String(name) => (&self.sql_ref, &self.qualified, name.as_str()),
6879 Value::Array(path) => match path.as_slice() {
6880 [Value::String(name)] => (&self.sql_ref, &self.qualified, name.as_str()),
6881 [Value::String(root), Value::String(name)] if root == "$" => {
6882 (&self.root_ref, &self.root_qualified, name.as_str())
6883 }
6884 _ => return Err(refused("or [\"$\", \"a column\"]")),
6885 },
6886 _ => return Err(refused("written as a string")),
6887 };
6888 let column = self
6891 .names
6892 .column_source(&qualified.schema, &qualified.name, name)
6893 .unwrap_or(name);
6894 Ok(format!(
6895 "{}.{}",
6896 against,
6897 postrust_sql::escape_ident(column)
6898 ))
6899 }
6900
6901 fn relationship(&self, name: &str) -> Option<std::borrow::Cow<'a, RelationshipField>> {
6915 let resolution = self.resolution.as_ref()?;
6916 let published = resolution
6917 .relationships
6918 .get(&self.type_name)
6919 .and_then(|fields| fields.iter().find(|r| r.name == name));
6920 if let Some(found) = published {
6921 return Some(std::borrow::Cow::Borrowed(found));
6922 }
6923
6924 let table = resolution.cache.get_table(&self.qualified)?;
6925
6926 if let Some(declared) = self
6932 .names
6933 .declared_relationships(&table.schema, &table.name)
6934 {
6935 if let Some(entry) = declared.iter().find(|entry| entry.name == name) {
6936 let target = entry
6939 .target(&table.schema)
6940 .map(|(_, target)| target)
6941 .unwrap_or_default();
6942 if let Some(field) = crate::schema::mapped_relationship_field(
6943 entry,
6944 table,
6945 resolution.cache,
6946 &table.schema,
6947 &target,
6948 &target,
6949 &target,
6950 ) {
6951 return Some(std::borrow::Cow::Owned(field));
6952 }
6953 if let Some(key) = entry.using.as_deref() {
6957 if let Some(found) = resolution
6958 .cache
6959 .get_relationships(&self.qualified, &table.schema)
6960 .into_iter()
6961 .flatten()
6962 .find(|rel| {
6963 crate::schema::relationship_keys(rel)
6964 .iter()
6965 .any(|found| found == key)
6966 })
6967 {
6968 return Some(std::borrow::Cow::Owned(
6969 crate::schema::relationship::RelationshipField::from_relationship_named(
6970 found,
6971 &found.foreign_table().name,
6972 Some(&entry.name),
6973 ),
6974 ));
6975 }
6976 }
6977 }
6978 }
6979
6980 resolution
6981 .cache
6982 .get_relationships(&self.qualified, &table.schema)?
6983 .iter()
6984 .find_map(|rel| {
6985 let given = crate::schema::relationship_keys(rel)
6986 .iter()
6987 .find_map(|key| self.names.relationship(&table.schema, &table.name, key));
6988 let field = RelationshipField::from_relationship_named(
6992 rel,
6993 &rel.foreign_table().name,
6994 given,
6995 );
6996 (field.name == name).then_some(std::borrow::Cow::Owned(field))
6997 })
6998 }
6999}
7000
7001fn column_comparison(op: &str) -> Option<&'static str> {
7010 Some(match op {
7011 "_ceq" => "=",
7012 "_cne" => "<>",
7013 "_cgt" => ">",
7014 "_cgte" => ">=",
7015 "_clt" => "<",
7016 "_clte" => "<=",
7017 _ => return None,
7018 })
7019}
7020
7021fn build_condition(
7023 value: &serde_json::Value,
7024 scope: &WhereScope<'_>,
7025 param_idx: &mut usize,
7026 values: &mut Vec<serde_json::Value>,
7027 alias_counter: &mut usize,
7028) -> Result<Option<String>, async_graphql::Error> {
7029 let serde_json::Value::Object(map) = value else {
7030 return Ok(None);
7031 };
7032
7033 let mut conditions: Vec<String> = Vec::new();
7034
7035 for (key, val) in map {
7036 match key.as_str() {
7037 "_and" | "_or" => {
7041 let members = val.as_array().ok_or_else(|| {
7042 async_graphql::Error::new(format!("\"{}\" takes a list of expressions", key))
7043 })?;
7044 let mut parts = Vec::new();
7045 for member in members {
7046 if let Some(sql) =
7047 build_condition(member, scope, param_idx, values, alias_counter)?
7048 {
7049 parts.push(sql);
7050 }
7051 }
7052 if parts.is_empty() {
7053 if key == "_or" {
7054 conditions.push("false".to_string());
7055 }
7056 continue;
7057 }
7058 let joiner = if key == "_and" { " AND " } else { " OR " };
7059 conditions.push(format!("({})", parts.join(joiner)));
7060 }
7061 "_not" => {
7062 if let Some(sql) = build_condition(val, scope, param_idx, values, alias_counter)? {
7063 conditions.push(format!("NOT ({})", sql));
7064 }
7065 }
7066 PERMISSION => {
7069 if let Some(sql) =
7070 build_condition(val, &scope.as_system(), param_idx, values, alias_counter)?
7071 {
7072 conditions.push(sql);
7073 }
7074 }
7075 "_exists" => {
7079 conditions.push(table_exists_sql(
7080 val,
7081 scope,
7082 param_idx,
7083 values,
7084 alias_counter,
7085 )?);
7086 }
7087 name if scope.relationship(name).is_some() => {
7093 let relationship = scope.relationship(name).expect("just checked");
7094 conditions.push(exists_sql(
7095 &relationship,
7096 val,
7097 scope,
7098 param_idx,
7099 values,
7100 alias_counter,
7101 )?);
7102 }
7103 name if scope.column_type(name).is_none()
7108 && name
7109 .strip_suffix("_aggregate")
7110 .and_then(|rel| scope.relationship(rel))
7111 .is_some_and(|rel| rel.is_list) =>
7112 {
7113 let relationship = name
7114 .strip_suffix("_aggregate")
7115 .and_then(|rel| scope.relationship(rel))
7116 .expect("just checked");
7117 conditions.push(aggregate_predicate_sql(
7118 &relationship,
7119 val,
7120 scope,
7121 param_idx,
7122 values,
7123 alias_counter,
7124 )?);
7125 }
7126 column => {
7127 let quoted = scope.column(column);
7128 match val {
7129 serde_json::Value::Object(ops) => {
7130 let column_type = scope.column_type(column);
7131 for (op, operand) in ops {
7132 if let Some(other) = column_comparison(op) {
7138 conditions.push(format!(
7139 "{} {} {}",
7140 quoted,
7141 other,
7142 scope.other_column(operand, op)?
7143 ));
7144 continue;
7145 }
7146 conditions.push(comparison_sql(
7147 "ed,
7148 column,
7149 column_type.as_deref(),
7150 op,
7151 operand,
7152 param_idx,
7153 values,
7154 )?);
7155 }
7156 }
7157 other => {
7161 conditions.push(format!("{} = ${}", quoted, param_idx));
7162 values.push(other.clone());
7163 *param_idx += 1;
7164 }
7165 }
7166 }
7167 }
7168 }
7169
7170 Ok(if conditions.is_empty() {
7171 None
7172 } else if conditions.len() == 1 {
7173 conditions.pop()
7174 } else {
7175 Some(conditions.join(" AND "))
7176 })
7177}
7178
7179fn exists_sql(
7186 relationship: &RelationshipField,
7187 child_expression: &serde_json::Value,
7188 scope: &WhereScope<'_>,
7189 param_idx: &mut usize,
7190 values: &mut Vec<serde_json::Value>,
7191 alias_counter: &mut usize,
7192) -> Result<String, async_graphql::Error> {
7193 let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7194 async_graphql::Error::new(format!(
7195 "filtering on the relationship \"{}\" is not available here",
7196 relationship.name
7197 ))
7198 })?;
7199
7200 let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7201 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7202
7203 if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7204 return Err(async_graphql::Error::new(format!(
7205 "filtering on \"{}\" is not supported: it is reached through a junction \
7206 rather than by a key",
7207 relationship.name
7208 )));
7209 }
7210
7211 *alias_counter += 1;
7212 let alias = format!("pgrst_rel_{}", alias_counter);
7213 let child_scope = WhereScope::aliased(
7214 &plan.foreign_schema,
7215 &plan.foreign_table,
7216 &alias,
7217 &relationship.target_type,
7218 scope,
7219 );
7220
7221 let (source, mut correlation) = match &plan.function {
7224 Some(function) => (
7225 format!(
7226 "{}.{}({})",
7227 postrust_sql::escape_ident(&function.schema),
7228 postrust_sql::escape_ident(&function.name),
7229 scope.row_ref
7230 ),
7231 Vec::new(),
7232 ),
7233 None => {
7234 let mut columns = Vec::with_capacity(plan.columns.len());
7235 for (parent_column, child_column) in &plan.columns {
7236 columns.push(format!(
7237 "{} = {}",
7238 scope.column(parent_column),
7239 child_scope.column(child_column)
7240 ));
7241 }
7242 (
7243 format!(
7244 "{}.{}",
7245 postrust_sql::escape_ident(&plan.foreign_schema),
7246 postrust_sql::escape_ident(&plan.foreign_table)
7247 ),
7248 columns,
7249 )
7250 }
7251 };
7252
7253 let child_condition = build_condition(
7254 child_expression,
7255 &child_scope,
7256 param_idx,
7257 values,
7258 alias_counter,
7259 )?;
7260 if let Some(sql) = child_condition {
7261 correlation.push(format!("({})", sql));
7262 }
7263
7264 if let Some(caller) = &child_scope.caller {
7269 let permission = permission_predicate(
7276 caller,
7277 child_scope.names,
7278 &plan.foreign_schema,
7279 &plan.foreign_table,
7280 )?;
7281 if let Some(predicate) = permission {
7282 if let Some(sql) =
7283 build_condition(&predicate, &child_scope, param_idx, values, alias_counter)?
7284 {
7285 correlation.push(format!("({})", sql));
7286 }
7287 }
7288 }
7289 if correlation.is_empty() {
7290 correlation.push("true".to_string());
7291 }
7292
7293 Ok(format!(
7294 "EXISTS (SELECT 1 FROM {} AS {} WHERE {})",
7295 source,
7296 postrust_sql::escape_ident(&alias),
7297 correlation.join(" AND ")
7298 ))
7299}
7300
7301fn table_exists_sql(
7326 spec: &serde_json::Value,
7327 scope: &WhereScope<'_>,
7328 param_idx: &mut usize,
7329 values: &mut Vec<serde_json::Value>,
7330 alias_counter: &mut usize,
7331) -> Result<String, async_graphql::Error> {
7332 let Some((schema, table)) = crate::role::exists_target(spec, &scope.qualified.schema) else {
7333 return Err(async_graphql::Error::new(
7334 "\"_exists\" needs a \"_table\" to look in",
7335 ));
7336 };
7337
7338 let qualified = postrust_core::api_request::QualifiedIdentifier::new(&schema, &table);
7342 if scope
7343 .resolution
7344 .as_ref()
7345 .is_some_and(|r| r.cache.get_table(&qualified).is_none())
7346 {
7347 return Err(async_graphql::Error::new(format!(
7348 "\"_exists\" names a table this server does not have: \"{}.{}\"",
7349 schema, table
7350 )));
7351 }
7352
7353 *alias_counter += 1;
7354 let alias = format!("pgrst_exists_{}", alias_counter);
7355 let type_name = scope
7359 .names
7360 .base_name(&schema, &table)
7361 .unwrap_or(&table)
7362 .to_string();
7363 let mut inner = WhereScope::aliased(&schema, &table, &alias, &type_name, scope);
7364 inner.caller = None;
7367
7368 let condition = match spec.get("_where") {
7369 Some(predicate) => build_condition(predicate, &inner, param_idx, values, alias_counter)?,
7370 None => None,
7371 };
7372
7373 Ok(format!(
7374 "EXISTS (SELECT 1 FROM {}.{} AS {} WHERE {})",
7375 postrust_sql::escape_ident(&schema),
7376 postrust_sql::escape_ident(&table),
7377 postrust_sql::escape_ident(&alias),
7378 condition.unwrap_or_else(|| "true".to_string())
7379 ))
7380}
7381
7382fn aggregate_predicate_sql(
7397 relationship: &RelationshipField,
7398 expression: &serde_json::Value,
7399 scope: &WhereScope<'_>,
7400 param_idx: &mut usize,
7401 values: &mut Vec<serde_json::Value>,
7402 alias_counter: &mut usize,
7403) -> Result<String, async_graphql::Error> {
7404 let serde_json::Value::Object(over) = expression else {
7405 return Ok("true".to_string());
7406 };
7407
7408 let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7409 async_graphql::Error::new(format!(
7410 "filtering on the relationship \"{}\" is not available here",
7411 relationship.name
7412 ))
7413 })?;
7414 let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7415 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7416 if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7417 return Err(async_graphql::Error::new(format!(
7418 "aggregating over \"{}\" is not supported: it is reached through a junction \
7419 rather than by a key",
7420 relationship.name
7421 )));
7422 }
7423
7424 let mut conditions: Vec<String> = Vec::new();
7425 for (function, spec) in over {
7426 if spec.is_null() {
7427 continue;
7428 }
7429 let serde_json::Value::Object(spec) = spec else {
7430 continue;
7431 };
7432 let Some(predicate) = spec.get("predicate").filter(|p| !p.is_null()) else {
7433 return Err(validation_error(format!(
7434 "\"{}\" over \"{}\" needs a predicate",
7435 function, relationship.name
7436 )));
7437 };
7438
7439 *alias_counter += 1;
7440 let alias = format!("pgrst_agg_{}", alias_counter);
7441 let child_scope = WhereScope::aliased(
7442 &plan.foreign_schema,
7443 &plan.foreign_table,
7444 &alias,
7445 &relationship.target_type,
7446 scope,
7447 );
7448
7449 let (source, mut correlation) = match &plan.function {
7452 Some(routine) => (
7453 format!(
7454 "{}.{}({})",
7455 postrust_sql::escape_ident(&routine.schema),
7456 postrust_sql::escape_ident(&routine.name),
7457 scope.row_ref
7458 ),
7459 Vec::new(),
7460 ),
7461 None => {
7462 let mut columns = Vec::with_capacity(plan.columns.len());
7463 for (parent_column, child_column) in &plan.columns {
7464 columns.push(format!(
7465 "{} = {}",
7466 scope.column(parent_column),
7467 child_scope.column(child_column)
7468 ));
7469 }
7470 (
7471 format!(
7472 "{}.{}",
7473 postrust_sql::escape_ident(&plan.foreign_schema),
7474 postrust_sql::escape_ident(&plan.foreign_table)
7475 ),
7476 columns,
7477 )
7478 }
7479 };
7480 if let Some(filter) = spec.get("filter").filter(|f| !f.is_null()) {
7483 if let Some(sql) =
7484 build_condition(filter, &child_scope, param_idx, values, alias_counter)?
7485 {
7486 correlation.push(format!("({})", sql));
7487 }
7488 }
7489 if correlation.is_empty() {
7490 correlation.push("true".to_string());
7491 }
7492
7493 let arguments = spec.get("arguments").filter(|a| !a.is_null());
7494 let distinct = matches!(spec.get("distinct"), Some(serde_json::Value::Bool(true)));
7495 let (aggregate, operand_type) = match function.as_str() {
7496 "count" => {
7497 let columns: Vec<String> = match arguments {
7498 Some(serde_json::Value::Array(items)) => items
7499 .iter()
7500 .filter_map(|item| item.as_str())
7501 .map(|column| child_scope.column(column))
7502 .collect(),
7503 Some(serde_json::Value::String(one)) => vec![child_scope.column(one)],
7504 _ => Vec::new(),
7505 };
7506 let counted = match columns.len() {
7507 0 => "*".to_string(),
7508 1 => columns.into_iter().next().unwrap_or_default(),
7509 _ => format!("({})", columns.join(", ")),
7510 };
7511 let counted = match distinct && counted != "*" {
7512 true => format!("DISTINCT {}", counted),
7513 false => counted,
7514 };
7515 (format!("count({})", counted), "integer")
7516 }
7517 "bool_and" | "bool_or" => {
7518 let Some(column) = arguments.and_then(|a| a.as_str()) else {
7519 return Err(validation_error(format!(
7520 "\"{}\" over \"{}\" needs the column to fold",
7521 function, relationship.name
7522 )));
7523 };
7524 (
7525 format!("{}({})", function, child_scope.column(column)),
7526 "boolean",
7527 )
7528 }
7529 other => {
7530 return Err(validation_error(format!(
7531 "\"{}\" is not an aggregate a predicate can be written over",
7532 other
7533 )))
7534 }
7535 };
7536
7537 let subselect = format!(
7538 "(SELECT {} FROM {} AS {} WHERE {})",
7539 aggregate,
7540 source,
7541 postrust_sql::escape_ident(&alias),
7542 correlation.join(" AND ")
7543 );
7544 let serde_json::Value::Object(operators) = predicate else {
7545 return Err(validation_error(format!(
7546 "the predicate on \"{}\" is not a comparison",
7547 function
7548 )));
7549 };
7550 for (operator, operand) in operators {
7551 conditions.push(comparison_sql(
7552 &subselect,
7553 function,
7554 Some(operand_type),
7555 operator,
7556 operand,
7557 param_idx,
7558 values,
7559 )?);
7560 }
7561 }
7562
7563 Ok(match conditions.is_empty() {
7564 true => "true".to_string(),
7565 false => format!("({})", conditions.join(" AND ")),
7566 })
7567}
7568
7569#[allow(clippy::too_many_arguments)]
7577fn postgis_sql(
7578 quoted: &str,
7579 column_type: Option<&str>,
7580 function: &str,
7581 op: &str,
7582 operand: &serde_json::Value,
7583 param_idx: &mut usize,
7584 values: &mut Vec<serde_json::Value>,
7585) -> Result<String, async_graphql::Error> {
7586 let is_geography = column_type == Some("geography");
7587 let mut shape = |value: &serde_json::Value, param_idx: &mut usize| -> String {
7588 let placeholder = format!("${}", param_idx);
7589 *param_idx += 1;
7590 values.push(value.clone());
7591 match is_geography {
7592 true => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
7593 false => format!("ST_GeomFromGeoJSON({})", placeholder),
7594 }
7595 };
7596
7597 match op {
7598 "_st_d_within" | "_st_3d_d_within" => {
7600 let serde_json::Value::Object(spec) = operand else {
7601 return Err(async_graphql::Error::new(format!(
7602 "\"{}\" takes {{distance, from}}",
7603 op
7604 )));
7605 };
7606 let Some(from) = spec.get("from") else {
7607 return Err(async_graphql::Error::new(format!(
7608 "\"{}\" needs a shape to measure from",
7609 op
7610 )));
7611 };
7612 let Some(distance) = spec.get("distance") else {
7613 return Err(async_graphql::Error::new(format!(
7614 "\"{}\" needs a distance",
7615 op
7616 )));
7617 };
7618 let from_sql = shape(from, param_idx);
7619 let distance_sql = format!("${}", param_idx);
7620 *param_idx += 1;
7621 values.push(distance.clone());
7622 let spheroid = match (is_geography, spec.get("use_spheroid")) {
7627 (true, Some(use_spheroid)) if !use_spheroid.is_null() => {
7628 let placeholder = format!("${}", param_idx);
7629 *param_idx += 1;
7630 values.push(use_spheroid.clone());
7631 format!(", {}::boolean", placeholder)
7632 }
7633 _ => String::new(),
7634 };
7635 Ok(format!(
7636 "{}({}, {}, {}::float8{})",
7637 function, quoted, from_sql, distance_sql, spheroid
7638 ))
7639 }
7640 "_st_intersects_rast" => {
7642 let placeholder = format!("${}", param_idx);
7643 *param_idx += 1;
7644 values.push(operand.clone());
7645 Ok(format!("{}({}, {}::raster)", function, quoted, placeholder))
7646 }
7647 "_st_intersects_geom_nband" | "_st_intersects_nband_geom" => {
7649 let serde_json::Value::Object(spec) = operand else {
7650 return Err(async_graphql::Error::new(format!(
7651 "\"{}\" takes an object",
7652 op
7653 )));
7654 };
7655 let Some(geometry) = spec.get("geommin") else {
7656 return Err(async_graphql::Error::new(format!(
7657 "\"{}\" needs a shape",
7658 op
7659 )));
7660 };
7661 let band = spec.get("nband").filter(|v| !v.is_null());
7662 let geometry_sql = shape(geometry, param_idx);
7663 Ok(match band {
7664 None => format!("{}({}, {})", function, quoted, geometry_sql),
7665 Some(band) => {
7666 let placeholder = format!("${}", param_idx);
7667 *param_idx += 1;
7668 values.push(band.clone());
7669 format!(
7673 "{}({}, {}::int, {})",
7674 function, quoted, placeholder, geometry_sql
7675 )
7676 }
7677 })
7678 }
7679 _ => {
7681 let other = shape(operand, param_idx);
7682 Ok(format!("{}({}, {})", function, quoted, other))
7683 }
7684 }
7685}
7686
7687fn sql_array(
7693 items: &[serde_json::Value],
7694 cast: &str,
7695 param_idx: &mut usize,
7696 values: &mut Vec<serde_json::Value>,
7697) -> String {
7698 if items.is_empty() {
7699 return format!("ARRAY[]::{}", cast);
7700 }
7701 let placeholders: Vec<String> = items
7702 .iter()
7703 .map(|item| {
7704 let placeholder = format!("${}", param_idx);
7705 *param_idx += 1;
7706 values.push(item.clone());
7707 placeholder
7708 })
7709 .collect();
7710 format!("ARRAY[{}]::{}", placeholders.join(", "), cast)
7711}
7712
7713#[allow(clippy::too_many_arguments)]
7715fn comparison_sql(
7716 quoted: &str,
7717 column: &str,
7718 column_type: Option<&str>,
7719 op: &str,
7720 operand: &serde_json::Value,
7721 param_idx: &mut usize,
7722 values: &mut Vec<serde_json::Value>,
7723) -> Result<String, async_graphql::Error> {
7724 if op == "_cast" {
7730 let serde_json::Value::Object(casts) = operand else {
7731 return Err(async_graphql::Error::new(
7732 "_cast takes an object naming the type to compare as",
7733 ));
7734 };
7735 let mut conditions = Vec::new();
7736 for (target, comparisons) in casts {
7737 let pg_type = match target.as_str() {
7741 "geometry" | "geography" => target.as_str(),
7742 "String" => "text",
7743 _ => {
7744 return Err(async_graphql::Error::new(format!(
7745 "cannot compare \"{}\" as \"{}\"",
7746 column, target
7747 )))
7748 }
7749 };
7750 let serde_json::Value::Object(ops) = comparisons else {
7751 continue;
7752 };
7753 let cast = format!("{}::{}", quoted, pg_type);
7754 for (nested_op, nested_operand) in ops {
7755 conditions.push(comparison_sql(
7756 &cast,
7757 column,
7758 Some(pg_type),
7759 nested_op,
7760 nested_operand,
7761 param_idx,
7762 values,
7763 )?);
7764 }
7765 }
7766 return Ok(match conditions.len() {
7767 0 => "true".to_string(),
7768 1 => conditions.pop().expect("just counted"),
7769 _ => format!("({})", conditions.join(" AND ")),
7770 });
7771 }
7772
7773 if let Some(function) = crate::input::bool_exp::postgis_function(op) {
7776 return postgis_sql(
7777 quoted,
7778 column_type,
7779 function,
7780 op,
7781 operand,
7782 param_idx,
7783 values,
7784 );
7785 }
7786
7787 if let Some((operator, cast)) = crate::input::bool_exp::ltree_operator(op) {
7791 if cast.ends_with("[]") {
7792 let items = operand.as_array().ok_or_else(|| {
7793 async_graphql::Error::new(format!(
7794 "the \"{}\" comparison on \"{}\" takes a list",
7795 op, column
7796 ))
7797 })?;
7798 return Ok(format!(
7799 "{} {} {}",
7800 quoted,
7801 operator,
7802 sql_array(items, cast, param_idx, values)
7803 ));
7804 }
7805 let placeholder = format!("${}", param_idx);
7806 *param_idx += 1;
7807 values.push(operand.clone());
7808 return Ok(format!("{} {} {}::{}", quoted, operator, placeholder, cast));
7809 }
7810
7811 if let Some(function) = match op {
7816 "_jsonb_path_exists" => Some("jsonb_path_exists"),
7817 "_jsonb_path_match" => Some("jsonb_path_match"),
7818 _ => None,
7819 } {
7820 let placeholder = format!("${}", param_idx);
7821 *param_idx += 1;
7822 values.push(operand.clone());
7823 return Ok(format!(
7824 "{}({}, {}::jsonpath)",
7825 function, quoted, placeholder
7826 ));
7827 }
7828
7829 let binary = match op {
7831 "_eq" => Some("="),
7832 "_neq" => Some("<>"),
7833 "_gt" => Some(">"),
7834 "_gte" => Some(">="),
7835 "_lt" => Some("<"),
7836 "_lte" => Some("<="),
7837 "_like" => Some("LIKE"),
7838 "_nlike" => Some("NOT LIKE"),
7839 "_ilike" => Some("ILIKE"),
7840 "_nilike" => Some("NOT ILIKE"),
7841 "_similar" => Some("SIMILAR TO"),
7842 "_nsimilar" => Some("NOT SIMILAR TO"),
7843 "_regex" => Some("~"),
7844 "_nregex" => Some("!~"),
7845 "_iregex" => Some("~*"),
7846 "_niregex" => Some("!~*"),
7847 "_contains" => Some("@>"),
7848 "_contained_in" => Some("<@"),
7849 "_has_key" => Some("?"),
7850 _ => None,
7851 };
7852
7853 if let Some(sql_op) = binary {
7854 let placeholder = format!("${}", param_idx);
7855 *param_idx += 1;
7856 values.push(match op {
7861 "_contains" | "_contained_in" => match operand {
7862 serde_json::Value::String(_)
7863 | serde_json::Value::Number(_)
7864 | serde_json::Value::Bool(_) => serde_json::Value::String(operand.to_string()),
7865 other => other.clone(),
7866 },
7867 _ => operand.clone(),
7868 });
7869 let cast = match op {
7881 "_contains" | "_contained_in" => "::jsonb".to_string(),
7882 "_has_key" => "::text".to_string(),
7883 _ if operand.is_null() => match column_type {
7892 Some(pg_type) => format!("::{}", pg_type),
7893 None => String::new(),
7894 },
7895 _ if operand.is_string() => match column_type {
7906 Some(pg_type) => format!("::{}", pg_type),
7907 None => String::new(),
7908 },
7909 _ => String::new(),
7910 };
7911 return Ok(format!("{} {} {}{}", quoted, sql_op, placeholder, cast));
7912 }
7913
7914 match op {
7915 "_is_null" => Ok(if operand.as_bool().unwrap_or(false) {
7916 format!("{} IS NULL", quoted)
7917 } else {
7918 format!("{} IS NOT NULL", quoted)
7919 }),
7920 "_in" | "_nin" => {
7921 let items = operand.as_array().ok_or_else(|| {
7922 async_graphql::Error::new(format!(
7923 "the \"{}\" comparison on \"{}\" requires a list of values",
7924 op, column
7925 ))
7926 })?;
7927 if items.is_empty() {
7928 return Ok(if op == "_in" { "false" } else { "true" }.to_string());
7931 }
7932 let mut placeholders = Vec::with_capacity(items.len());
7933 for item in items {
7934 let cast = match (item.is_null() || item.is_string(), column_type) {
7937 (true, Some(pg_type)) => format!("::{}", pg_type),
7938 _ => String::new(),
7939 };
7940 placeholders.push(format!("${}{}", param_idx, cast));
7941 values.push(item.clone());
7942 *param_idx += 1;
7943 }
7944 Ok(format!(
7945 "{} {} ({})",
7946 quoted,
7947 if op == "_in" { "IN" } else { "NOT IN" },
7948 placeholders.join(", ")
7949 ))
7950 }
7951 "_has_keys_any" | "_has_keys_all" => {
7952 let items = operand.as_array().ok_or_else(|| {
7953 async_graphql::Error::new(format!(
7954 "the \"{}\" comparison on \"{}\" requires a list of keys",
7955 op, column
7956 ))
7957 })?;
7958 Ok(format!(
7959 "{} {} {}",
7960 quoted,
7961 if op == "_has_keys_any" { "?|" } else { "?&" },
7962 sql_array(items, "text[]", param_idx, values)
7963 ))
7964 }
7965 other => Err(async_graphql::Error::new(format!(
7966 "unsupported comparison \"{}\" on \"{}\"",
7967 other, column
7968 ))),
7969 }
7970}
7971
7972#[allow(clippy::too_many_arguments)] async fn build_order_by_clause(
7982 ctx: &ResolverContext<'_>,
7983 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
7984 schema_name: &str,
7985 table_name: &str,
7986 type_name: &str,
7987 relationships: &HashMap<String, Vec<RelationshipField>>,
7988 reference: &str,
7989 names: &crate::names::NameOverrides,
7990) -> Result<String, async_graphql::Error> {
7991 let Ok(order_arg) = ctx.args.try_get("order_by") else {
7992 return Ok(String::new());
7993 };
7994
7995 let value = accessor_to_json(&order_arg);
7996 let entries: Vec<&serde_json::Value> = match &value {
7999 serde_json::Value::Array(items) => items.iter().collect(),
8000 serde_json::Value::Object(_) => vec![&value],
8001 serde_json::Value::Null => return Ok(String::new()),
8002 _ => {
8003 return Err(async_graphql::Error::new(
8004 "order_by takes an object or a list of them",
8005 ))
8006 }
8007 };
8008 if entries.is_empty() {
8009 return Ok(String::new());
8010 }
8011
8012 let guard = schema_cache
8013 .get()
8014 .await
8015 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8016 let cache = guard
8017 .as_ref()
8018 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8019 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8020 let table = cache
8021 .get_table(&qi)
8022 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8023
8024 let mut terms = Vec::new();
8025 let mut alias_counter = 0usize;
8026 for entry in entries {
8027 order_terms_into(
8028 entry,
8029 cache,
8030 relationships,
8031 type_name,
8032 table,
8033 reference,
8034 names,
8035 &mut alias_counter,
8036 &mut terms,
8037 )?;
8038 }
8039
8040 if terms.is_empty() {
8041 return Ok(String::new());
8042 }
8043 Ok(format!(" ORDER BY {}", terms.join(", ")))
8044}
8045
8046#[allow(clippy::too_many_arguments)]
8054fn order_terms_into(
8055 entry: &serde_json::Value,
8056 cache: &SchemaCache,
8057 relationships: &HashMap<String, Vec<RelationshipField>>,
8058 type_name: &str,
8059 table: &postrust_core::schema_cache::Table,
8060 reference: &str,
8061 names: &crate::names::NameOverrides,
8062 alias_counter: &mut usize,
8063 terms: &mut Vec<String>,
8064) -> Result<(), async_graphql::Error> {
8065 let serde_json::Value::Object(map) = entry else {
8066 return Err(async_graphql::Error::new(
8067 "each order_by entry is an object mapping a column to a direction",
8068 ));
8069 };
8070 let available: &[RelationshipField] = relationships
8071 .get(type_name)
8072 .map(|r| r.as_slice())
8073 .unwrap_or(&[]);
8074
8075 for (key, value) in map {
8076 if value.is_null() {
8082 continue;
8083 }
8084
8085 if let Some(name) = value.as_str() {
8087 let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8088 async_graphql::Error::new(format!(
8089 "\"{}\" is not a sort direction; expected one of asc, desc, \
8090 asc_nulls_first, asc_nulls_last, desc_nulls_first, desc_nulls_last",
8091 name
8092 ))
8093 })?;
8094 let column = table_column_for(names, table, key);
8095 if table.get_column(column).is_some() {
8096 terms.push(format!(
8097 "{}.{} {}",
8098 reference,
8099 postrust_sql::escape_ident(column),
8100 sql
8101 ));
8102 continue;
8103 }
8104 let function = names
8107 .computed_source(&table.schema, &table.name, key)
8108 .unwrap_or(key);
8109 if let Some(definition) = table.get_computed_column(function) {
8110 terms.push(format!(
8111 "{} {}",
8112 computed_call(definition, &format!("{}.*", reference), reference),
8113 sql
8114 ));
8115 continue;
8116 }
8117 return Err(async_graphql::Error::new(format!(
8118 "cannot order by unknown column \"{}\" on \"{}\"",
8119 key, table.name
8120 )));
8121 }
8122
8123 if let Some(rel) = key
8125 .strip_suffix("_aggregate")
8126 .and_then(|name| available.iter().find(|r| r.name == name && r.is_list))
8127 {
8128 aggregate_order_terms(value, rel, cache, reference, alias_counter, terms)?;
8129 continue;
8130 }
8131
8132 if let Some(rel) = available.iter().find(|r| r.name == *key && !r.is_list) {
8134 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8135 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8136 if plan.columns.is_empty() {
8137 return Err(async_graphql::Error::new(format!(
8138 "cannot order by \"{}\": it is not reached by a key",
8139 key
8140 )));
8141 }
8142 *alias_counter += 1;
8143 let alias = format!("pgrst_ord_{}", alias_counter);
8144 let quoted_alias = postrust_sql::escape_ident(&alias);
8145 let correlation = plan
8146 .columns
8147 .iter()
8148 .map(|(local, foreign)| {
8149 format!(
8150 "{} = {}.{}",
8151 format_args!("{}.{}", reference, postrust_sql::escape_ident(local)),
8152 quoted_alias,
8153 postrust_sql::escape_ident(foreign)
8154 )
8155 })
8156 .collect::<Vec<_>>()
8157 .join(" AND ");
8158
8159 let target_qi = postrust_core::api_request::QualifiedIdentifier::new(
8160 &plan.foreign_schema,
8161 &plan.foreign_table,
8162 );
8163 let Some(target) = cache.get_table(&target_qi) else {
8164 return Err(async_graphql::Error::new(format!(
8165 "unknown table \"{}\"",
8166 plan.foreign_table
8167 )));
8168 };
8169
8170 let mut nested = Vec::new();
8173 order_terms_into(
8174 value,
8175 cache,
8176 relationships,
8177 &rel.target_type,
8178 target,
8179 "ed_alias,
8180 names,
8181 alias_counter,
8182 &mut nested,
8183 )?;
8184 for term in nested {
8185 let (expression, direction) = term
8186 .rsplit_once(' ')
8187 .map(|(e, d)| (e.to_string(), d.to_string()))
8188 .unwrap_or((term.clone(), String::new()));
8189 terms.push(format!(
8190 "(SELECT {} FROM {}.{} AS {} WHERE {}) {}",
8191 expression,
8192 postrust_sql::escape_ident(&plan.foreign_schema),
8193 postrust_sql::escape_ident(&plan.foreign_table),
8194 quoted_alias,
8195 correlation,
8196 direction
8197 ));
8198 }
8199 continue;
8200 }
8201
8202 return Err(async_graphql::Error::new(format!(
8203 "cannot order by \"{}\" on \"{}\"",
8204 key, table.name
8205 )));
8206 }
8207 Ok(())
8208}
8209
8210fn aggregate_order_terms(
8212 value: &serde_json::Value,
8213 rel: &RelationshipField,
8214 cache: &SchemaCache,
8215 reference: &str,
8216 alias_counter: &mut usize,
8217 terms: &mut Vec<String>,
8218) -> Result<(), async_graphql::Error> {
8219 let serde_json::Value::Object(spec) = value else {
8220 return Err(async_graphql::Error::new(
8221 "ordering by an aggregate takes an object, such as {count: desc}",
8222 ));
8223 };
8224 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8225 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8226 if plan.columns.is_empty() && plan.function.is_none() {
8227 return Err(async_graphql::Error::new(format!(
8228 "cannot order by an aggregate of \"{}\": it is not reached by a key",
8229 rel.name
8230 )));
8231 }
8232 if plan.function.is_some() && !rel.arguments.is_empty() {
8235 return Err(async_graphql::Error::new(format!(
8236 "cannot order by an aggregate of \"{}\": it takes arguments, and an \
8237 ordering has nowhere to write them",
8238 rel.name
8239 )));
8240 }
8241
8242 *alias_counter += 1;
8243 let alias = postrust_sql::escape_ident(&format!("pgrst_ord_{}", alias_counter));
8244 let (source, correlation) = match &plan.function {
8247 Some(function) => {
8248 let session = plan.row_argument.as_ref().and_then(|row_argument| {
8249 computed_session_argument(cache, function, row_argument)
8250 .map(|session| (row_argument.clone(), session))
8251 });
8252 let call = match session {
8253 Some((row_argument, session)) => format!(
8254 "{} => {}, {} => {}",
8255 postrust_sql::escape_ident(&row_argument),
8256 reference,
8257 postrust_sql::escape_ident(&session),
8258 SESSION_ARGUMENT
8259 ),
8260 None => reference.to_string(),
8261 };
8262 (
8263 format!(
8264 "{}.{}({})",
8265 postrust_sql::escape_ident(&function.schema),
8266 postrust_sql::escape_ident(&function.name),
8267 call
8268 ),
8269 "true".to_string(),
8270 )
8271 }
8272 None => (
8273 format!(
8274 "{}.{}",
8275 postrust_sql::escape_ident(&plan.foreign_schema),
8276 postrust_sql::escape_ident(&plan.foreign_table)
8277 ),
8278 plan.columns
8279 .iter()
8280 .map(|(local, foreign)| {
8281 format!(
8282 "{}.{} = {}.{}",
8283 reference,
8284 postrust_sql::escape_ident(local),
8285 alias,
8286 postrust_sql::escape_ident(foreign)
8287 )
8288 })
8289 .collect::<Vec<_>>()
8290 .join(" AND "),
8291 ),
8292 };
8293
8294 let mut wanted: Vec<(String, String)> = Vec::new();
8296 for (function, argument) in spec {
8297 if argument.is_null() {
8300 continue;
8301 }
8302 if function == "count" {
8303 let name = argument.as_str().unwrap_or_default();
8304 let Some(direction) = crate::input::order_by::direction_sql(name) else {
8305 return Err(async_graphql::Error::new(format!(
8306 "\"{}\" is not a sort direction",
8307 name
8308 )));
8309 };
8310 wanted.push(("count(*)".to_string(), direction.to_string()));
8311 continue;
8312 }
8313 let serde_json::Value::Object(columns) = argument else {
8314 continue;
8315 };
8316 for (column, direction) in columns {
8317 if direction.is_null() {
8318 continue;
8319 }
8320 let name = direction.as_str().unwrap_or_default();
8321 let Some(direction) = crate::input::order_by::direction_sql(name) else {
8322 return Err(async_graphql::Error::new(format!(
8323 "\"{}\" is not a sort direction",
8324 name
8325 )));
8326 };
8327 wanted.push((
8330 format!(
8331 "{}({}.{})",
8332 function,
8333 alias,
8334 postrust_sql::escape_ident(column)
8335 ),
8336 direction.to_string(),
8337 ));
8338 }
8339 }
8340
8341 for (expression, direction) in wanted {
8342 terms.push(format!(
8343 "(SELECT {} FROM {} AS {} WHERE {}) {}",
8344 expression, source, alias, correlation, direction
8345 ));
8346 }
8347 Ok(())
8348}
8349
8350fn distinct_on_clause(
8364 distinct_on: &[String],
8365 written: Option<&str>,
8366) -> Result<(String, Option<String>), async_graphql::Error> {
8367 if distinct_on.is_empty() {
8368 return Ok((String::new(), written.map(str::to_string)));
8369 }
8370 let terms: Vec<&str> = written
8371 .map(|written| written.split(", ").collect())
8372 .unwrap_or_default();
8373 let column_of = |term: &str| {
8376 let expression = term.split_whitespace().next().unwrap_or(term);
8377 expression
8378 .rsplit_once('.')
8379 .map(|(_, name)| name)
8380 .unwrap_or(expression)
8381 .to_string()
8382 };
8383 let leading: Vec<String> = terms
8384 .iter()
8385 .take(distinct_on.len())
8386 .map(|term| column_of(term))
8387 .collect();
8388 if !terms.is_empty()
8389 && (leading.len() != distinct_on.len()
8390 || distinct_on
8391 .iter()
8392 .any(|column| !leading.contains(&column_of(column))))
8393 {
8394 return Err(validation_error(
8395 "\"distinct_on\" columns must match initial \"order_by\" columns",
8396 ));
8397 }
8398 let order = match terms.is_empty() {
8399 true => Some(distinct_on.join(", ")),
8400 false => written.map(str::to_string),
8401 };
8402 Ok((format!("DISTINCT ON ({}) ", distinct_on.join(", ")), order))
8403}
8404
8405async fn build_distinct_on(
8413 ctx: &ResolverContext<'_>,
8414 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8415 schema_name: &str,
8416 table_name: &str,
8417 names: &crate::names::NameOverrides,
8418) -> Result<Vec<String>, async_graphql::Error> {
8419 let Ok(arg) = ctx.args.try_get("distinct_on") else {
8420 return Ok(Vec::new());
8421 };
8422 let value = accessor_to_json(&arg);
8423 let requested: Vec<String> = match &value {
8424 serde_json::Value::Array(items) => items
8425 .iter()
8426 .filter_map(|i| i.as_str().map(|s| s.to_string()))
8427 .collect(),
8428 serde_json::Value::String(one) => vec![one.clone()],
8429 _ => Vec::new(),
8430 };
8431 if requested.is_empty() {
8432 return Ok(Vec::new());
8433 }
8434
8435 let guard = schema_cache
8436 .get()
8437 .await
8438 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8439 let cache = guard
8440 .as_ref()
8441 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8442 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8443 let table = cache
8444 .get_table(&qi)
8445 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8446
8447 let mut quoted = Vec::with_capacity(requested.len());
8448 for field in requested {
8449 let name = table_column_for(names, table, &field).to_string();
8450 if table.get_column(&name).is_none() {
8451 return Err(async_graphql::Error::new(format!(
8452 "cannot take distinct on unknown column \"{}\" of \"{}\"",
8453 field, table_name
8454 )));
8455 }
8456 quoted.push(postrust_sql::escape_ident(&name));
8457 }
8458 Ok(quoted)
8459}
8460
8461#[allow(clippy::too_many_arguments)] fn computed_projections<'a>(
8475 table: &postrust_core::schema_cache::Table,
8476 fields: impl IntoIterator<Item = SelectionField<'a>>,
8477 row_reference: &str,
8478 names: &crate::names::NameOverrides,
8479 schema_cache: &SchemaCache,
8480 param_idx: &mut usize,
8481 values: &mut Vec<serde_json::Value>,
8482) -> Result<Vec<String>, async_graphql::Error> {
8483 let mut projections = Vec::new();
8484 for field in fields {
8485 let name = field.name();
8486 if table
8489 .get_column(table_column_for(names, table, name))
8490 .is_some()
8491 {
8492 continue;
8493 }
8494 let function = names
8497 .computed_source(&table.schema, &table.name, name)
8498 .unwrap_or(name);
8499 let Some(computed) = table.get_computed_column(function) else {
8500 continue;
8501 };
8502 let arguments = crate::schema::computed_caller_arguments(computed, schema_cache);
8503 let given = match arguments.is_empty() {
8504 true => None,
8505 false => embed_arguments(field).remove("args"),
8506 };
8507 projections.push(format!(
8508 "{} AS {}",
8509 computed_column_call(
8510 computed,
8511 row_reference,
8512 name,
8513 &arguments,
8514 given.as_ref(),
8515 param_idx,
8516 values,
8517 )?,
8518 postrust_sql::escape_ident(name)
8519 ));
8520 }
8521 Ok(projections)
8522}
8523
8524fn computed_column_call(
8533 computed: &postrust_core::schema_cache::ComputedColumn,
8534 row: &str,
8535 field_name: &str,
8536 arguments: &[(String, String, bool)],
8537 given: Option<&serde_json::Value>,
8538 param_idx: &mut usize,
8539 values: &mut Vec<serde_json::Value>,
8540) -> Result<String, async_graphql::Error> {
8541 if arguments.is_empty() {
8542 return Ok(computed_call(computed, row, row));
8543 }
8544 let Some(row_argument) = &computed.row_argument else {
8545 return Ok(computed_call(computed, row, row));
8546 };
8547 let function = format!(
8548 "{}.{}",
8549 postrust_sql::escape_ident(&computed.function.schema),
8550 postrust_sql::escape_ident(&computed.function.name)
8551 );
8552 let mut passed = vec![format!(
8553 "{} => {}",
8554 postrust_sql::escape_ident(row_argument),
8555 row
8556 )];
8557 if let Some(session) = &computed.session_argument {
8558 passed.push(format!(
8559 "{} => {}",
8560 postrust_sql::escape_ident(session),
8561 SESSION_ARGUMENT
8562 ));
8563 }
8564 for (name, pg_type, required) in arguments {
8565 match given.and_then(|given| given.get(name)) {
8566 None if *required => {
8567 return Err(async_graphql::Error::new(format!(
8568 "{} needs the argument \"{}\"",
8569 field_name, name
8570 )))
8571 }
8572 None => continue,
8574 Some(value) => {
8575 passed.push(format!(
8576 "{} => ${}::{}",
8577 postrust_sql::escape_ident(name),
8578 param_idx,
8579 pg_type
8580 ));
8581 *param_idx += 1;
8582 values.push(value.clone());
8583 }
8584 }
8585 }
8586 Ok(format!("{}({})", function, passed.join(", ")))
8587}
8588
8589fn count_expression(
8601 field: async_graphql::SelectionField<'_>,
8602 table: (&str, &str),
8603 alias: Option<&str>,
8604 names: &crate::names::NameOverrides,
8605) -> String {
8606 let arguments = embed_arguments(field);
8607 let requested = match arguments.get("columns") {
8608 Some(serde_json::Value::Array(items)) => items
8609 .iter()
8610 .filter_map(|item| item.as_str())
8611 .collect::<Vec<_>>(),
8612 Some(serde_json::Value::String(one)) => vec![one.as_str()],
8613 _ => Vec::new(),
8614 };
8615 if requested.is_empty() {
8616 return "count(*)".to_string();
8617 }
8618 let columns: Vec<String> = requested
8619 .into_iter()
8620 .map(|column| {
8621 let (schema, name) = table;
8622 let source = column_for(names, schema, name, column);
8623 match alias {
8624 Some(alias) => format!(
8625 "{}.{}",
8626 postrust_sql::escape_ident(alias),
8627 postrust_sql::escape_ident(source)
8628 ),
8629 None => postrust_sql::escape_ident(source),
8630 }
8631 })
8632 .collect();
8633 let counted = match columns.len() {
8636 1 => columns.into_iter().next().unwrap_or_default(),
8637 _ => format!("({})", columns.join(", ")),
8638 };
8639 let distinct = matches!(
8640 arguments.get("distinct"),
8641 Some(serde_json::Value::Bool(true))
8642 );
8643 match distinct {
8644 true => format!("count(DISTINCT {})", counted),
8645 false => format!("count({})", counted),
8646 }
8647}
8648
8649#[allow(clippy::too_many_arguments)] fn nested_aggregate_select(
8657 caller: &crate::role::Caller<'_>,
8658 selection: async_graphql::SelectionField<'_>,
8659 child_alias: &str,
8660 schema_cache: &SchemaCache,
8661 relationships: &HashMap<String, Vec<RelationshipField>>,
8662 target_type: &str,
8663 child_table: Option<&postrust_core::schema_cache::Table>,
8664 max_rows: Option<i64>,
8665 alias_counter: &mut usize,
8666 param_idx: &mut usize,
8667 values: &mut Vec<serde_json::Value>,
8668 names: &crate::names::NameOverrides,
8669) -> Result<String, async_graphql::Error> {
8670 use crate::schema::aggregate as agg;
8671
8672 let mut parts: Vec<String> = Vec::new();
8673
8674 for child in selection.selection_set() {
8675 match child.name() {
8676 "aggregate" => {
8677 let of_table = child_table
8678 .map(|table| (table.schema.as_str(), table.name.as_str()))
8679 .unwrap_or(("", ""));
8680 let mut counts: Vec<(String, String)> = Vec::new();
8685 let mut wanted: BTreeMap<&str, Vec<String>> = BTreeMap::new();
8686 for function in child.selection_set() {
8687 if function.name() == "count" {
8688 counts.push((
8689 function.alias().unwrap_or("count").to_string(),
8690 count_expression(function, of_table, Some(child_alias), names),
8691 ));
8692 continue;
8693 }
8694 let sql_function = agg::NUMERIC_AGGREGATES
8695 .iter()
8696 .chain(agg::ORDERED_AGGREGATES.iter())
8697 .find(|(name, _)| *name == function.name())
8698 .map(|(name, _)| *name);
8699 let Some(sql_function) = sql_function else {
8700 continue;
8701 };
8702 let columns = wanted.entry(sql_function).or_default();
8703 for column in function.selection_set() {
8704 let name = column.name().to_string();
8705 if !columns.contains(&name) {
8706 columns.push(name);
8707 }
8708 }
8709 }
8710 let mut fields = match counts.iter().any(|(key, _)| key == "count") {
8713 true => Vec::new(),
8714 false => vec!["'count', count(*)".to_string()],
8715 };
8716 for (key, expression) in &counts {
8717 fields.push(format!("'{}', {}", key.replace('\'', "''"), expression));
8718 }
8719 for (sql_function, named) in &wanted {
8720 let columns: Vec<String> = named
8721 .iter()
8722 .map(|column| {
8723 let source = child_table
8724 .map(|table| table_column_for(names, table, column))
8725 .unwrap_or(column.as_str());
8726 format!(
8727 "'{}', {}({}.{})",
8728 column.replace('\'', "''"),
8729 sql_function,
8730 postrust_sql::escape_ident(child_alias),
8731 postrust_sql::escape_ident(source)
8732 )
8733 })
8734 .collect();
8735 if !columns.is_empty() {
8736 fields.push(format!(
8737 "'{}', json_build_object({})",
8738 sql_function,
8739 columns.join(", ")
8740 ));
8741 }
8742 }
8743 parts.push(format!(
8744 "json_build_object({}) AS {}",
8745 fields.join(", "),
8746 postrust_sql::escape_ident("aggregate")
8747 ));
8748 }
8749 "nodes" => {
8754 let embeds = build_embed_expressions(
8755 caller,
8756 schema_cache,
8757 relationships,
8758 target_type,
8759 child_alias,
8760 child.selection_set(),
8761 max_rows,
8762 alias_counter,
8763 param_idx,
8764 values,
8765 names,
8766 )?;
8767 let mut columns: Vec<String> = Vec::new();
8768 for column in child.selection_set() {
8769 let name = column.name();
8770 if name == "__typename" || embeds.iter().any(|(field, _)| field == name) {
8771 continue;
8772 }
8773 columns.push(format!(
8774 "'{}', {}",
8775 name.replace('\'', "''"),
8776 column_expression(child_table, child_alias, name, names)
8777 ));
8778 }
8779 for (field, expression) in &embeds {
8780 columns.push(format!("'{}', {}", field.replace('\'', "''"), expression));
8781 }
8782 let row = if columns.is_empty() {
8783 format!("row_to_json({})", postrust_sql::escape_ident(child_alias))
8784 } else {
8785 format!("json_build_object({})", columns.join(", "))
8786 };
8787 parts.push(format!(
8788 "COALESCE(json_agg({}), '[]'::json) AS {}",
8789 row,
8790 postrust_sql::escape_ident("nodes")
8791 ));
8792 }
8793 _ => {}
8794 }
8795 }
8796
8797 if parts.is_empty() {
8798 parts.push("count(*) AS pgrst_empty".to_string());
8801 }
8802 Ok(parts.join(", "))
8803}
8804
8805fn column_expression(
8810 table: Option<&postrust_core::schema_cache::Table>,
8811 alias: &str,
8812 name: &str,
8813 names: &crate::names::NameOverrides,
8814) -> String {
8815 let column = table
8816 .map(|t| table_column_for(names, t, name))
8817 .unwrap_or(name);
8818 let plain = format!(
8819 "{}.{}",
8820 postrust_sql::escape_ident(alias),
8821 postrust_sql::escape_ident(column)
8822 );
8823 let computed = table
8824 .filter(|t| t.get_column(column).is_none())
8825 .and_then(|t| {
8826 let function = names
8827 .computed_source(&t.schema, &t.name, name)
8828 .unwrap_or(name);
8829 t.get_computed_column(function)
8830 });
8831 match computed {
8832 Some(definition) => computed_call(
8833 definition,
8834 &format!("{}.*", postrust_sql::escape_ident(alias)),
8835 &postrust_sql::escape_ident(alias),
8836 ),
8837 None => plain,
8838 }
8839}
8840
8841fn order_terms(
8849 order: &serde_json::Value,
8850 schema_cache: &SchemaCache,
8851 schema_name: &str,
8852 table_name: &str,
8853 alias: &str,
8854 names: &crate::names::NameOverrides,
8855) -> Result<Option<String>, async_graphql::Error> {
8856 let entries: Vec<&serde_json::Value> = match order {
8857 serde_json::Value::Array(items) => items.iter().collect(),
8858 serde_json::Value::Object(_) => vec![order],
8859 _ => return Ok(None),
8860 };
8861
8862 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8863 let table = schema_cache
8864 .get_table(&qi)
8865 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8866
8867 let mut terms = Vec::new();
8868 for entry in entries {
8869 let serde_json::Value::Object(map) = entry else {
8870 continue;
8871 };
8872 for (field, direction) in map {
8873 if direction.is_null() {
8875 continue;
8876 }
8877 let column = table_column_for(names, table, field);
8878 if table.get_column(column).is_none() {
8879 return Err(async_graphql::Error::new(format!(
8880 "cannot order by unknown column \"{}\" on \"{}\"",
8881 field, table_name
8882 )));
8883 }
8884 let name = direction.as_str().unwrap_or_default();
8885 let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8886 async_graphql::Error::new(format!("\"{}\" is not a sort direction", name))
8887 })?;
8888 terms.push(format!(
8889 "{}.{} {}",
8890 postrust_sql::escape_ident(alias),
8891 postrust_sql::escape_ident(column),
8892 sql
8893 ));
8894 }
8895 }
8896
8897 Ok(if terms.is_empty() {
8898 None
8899 } else {
8900 Some(terms.join(", "))
8901 })
8902}
8903
8904fn always_present(rel: &RelationshipField, table: &postrust_core::schema_cache::Table) -> bool {
8916 use postrust_core::schema_cache::{Cardinality, Relationship};
8917 let Relationship::ForeignKey { cardinality, .. } = &rel.relationship else {
8918 return false;
8919 };
8920 match cardinality {
8921 Cardinality::M2O { .. } => {}
8922 Cardinality::O2O {
8923 is_parent: false, ..
8924 } => {}
8925 _ => return false,
8926 }
8927 let columns = cardinality.columns();
8928 !columns.is_empty()
8929 && columns.iter().all(|(local, _)| {
8930 table
8931 .get_column(local)
8932 .is_some_and(|column| !column.nullable)
8933 })
8934}
8935
8936fn child_holds_the_key(relationship: &postrust_core::schema_cache::Relationship) -> bool {
8944 use postrust_core::schema_cache::{Cardinality, Relationship};
8945 match relationship {
8946 Relationship::ForeignKey { cardinality, .. } => match cardinality {
8947 Cardinality::M2O { .. } => false,
8948 Cardinality::O2O { is_parent, .. } => *is_parent,
8949 _ => true,
8950 },
8951 Relationship::Computed { .. } => true,
8952 }
8953}
8954
8955fn computed_session_argument(
8957 cache: &SchemaCache,
8958 function: &postrust_core::api_request::QualifiedIdentifier,
8959 row_argument: &str,
8960) -> Option<String> {
8961 let _ = row_argument;
8962 cache
8963 .routines
8964 .get(function)
8965 .into_iter()
8966 .flatten()
8967 .find(|routine| routine.name == function.name)?
8968 .params
8969 .iter()
8970 .find(|param| {
8971 param.name == "hasura_session" && matches!(param.param_type.as_str(), "json" | "jsonb")
8972 })
8973 .map(|param| param.name.clone())
8974}
8975
8976fn computed_args_type_name(type_name: &str, field: &str) -> String {
8978 format!("{}_{}_args", type_name, field)
8979}
8980
8981fn computed_arguments(
8988 relationship: &RelationshipField,
8989 plan: &postrust_core::embed::EmbedPlan,
8990 parent_row: &str,
8991 given: Option<&serde_json::Value>,
8992 param_idx: &mut usize,
8993 values: &mut Vec<serde_json::Value>,
8994 schema_cache: &SchemaCache,
8995) -> Result<Option<String>, async_graphql::Error> {
8996 let (Some(function), Some(row_argument)) = (&plan.function, &plan.row_argument) else {
8997 return Ok(None);
8998 };
8999 let session = schema_cache
9000 .routines
9001 .get(function)
9002 .into_iter()
9003 .flatten()
9004 .find(|routine| routine.name == function.name)
9005 .and_then(|routine| {
9006 routine
9007 .params
9008 .iter()
9009 .find(|param| {
9010 param.name == "hasura_session"
9011 && matches!(param.param_type.as_str(), "json" | "jsonb")
9012 })
9013 .map(|param| param.name.clone())
9014 });
9015 if relationship.arguments.is_empty() && session.is_none() {
9016 return Ok(None);
9017 }
9018
9019 let mut passed = vec![format!(
9020 "{} => {}",
9021 postrust_sql::escape_ident(row_argument),
9022 parent_row
9023 )];
9024 if let Some(session) = session {
9025 passed.push(format!(
9026 "{} => {}",
9027 postrust_sql::escape_ident(&session),
9028 SESSION_ARGUMENT
9029 ));
9030 }
9031 for (name, pg_type, required) in &relationship.arguments {
9032 match given.and_then(|given| given.get(name)) {
9033 None if *required => {
9034 return Err(async_graphql::Error::new(format!(
9035 "{} needs the argument \"{}\"",
9036 relationship.name, name
9037 )))
9038 }
9039 None => continue,
9041 Some(value) => {
9042 passed.push(format!(
9043 "{} => ${}::{}",
9044 postrust_sql::escape_ident(name),
9045 param_idx,
9046 pg_type
9047 ));
9048 *param_idx += 1;
9049 values.push(value.clone());
9050 }
9051 }
9052 }
9053 Ok(Some(passed.join(", ")))
9054}
9055
9056fn embed_arguments(field: async_graphql::SelectionField<'_>) -> HashMap<String, serde_json::Value> {
9058 field
9059 .arguments()
9060 .map(|args| {
9061 args.into_iter()
9062 .map(|(name, value)| {
9063 (
9064 name.to_string(),
9065 plain_numbers(value.into_json().unwrap_or(serde_json::Value::Null)),
9066 )
9067 })
9068 .collect()
9069 })
9070 .unwrap_or_default()
9071}
9072
9073type Narrowing = (String, Option<String>, Option<String>, Option<i64>, i64);
9077
9078#[allow(clippy::too_many_arguments)] fn embed_narrowing(
9085 arguments: &HashMap<String, serde_json::Value>,
9086 plan: &postrust_core::embed::EmbedPlan,
9087 schema_cache: &SchemaCache,
9088 relationships: &HashMap<String, Vec<RelationshipField>>,
9089 target_type: &str,
9090 child_alias: &str,
9091 max_rows: Option<i64>,
9092 param_idx: &mut usize,
9093 values: &mut Vec<serde_json::Value>,
9094 names: &crate::names::NameOverrides,
9095 caller: &crate::role::Caller<'_>,
9096) -> Result<Narrowing, async_graphql::Error> {
9097 let permission =
9101 permission_predicate(caller, names, &plan.foreign_schema, &plan.foreign_table)?;
9102 let requested = arguments.get("where").filter(|v| !v.is_null()).cloned();
9103 let child_where = match and_predicates(requested, permission) {
9104 Some(expression) => {
9105 let child_scope = WhereScope::for_alias(
9106 &plan.foreign_schema,
9107 &plan.foreign_table,
9108 child_alias,
9109 target_type,
9110 schema_cache,
9111 relationships,
9112 names,
9113 )
9114 .for_caller(*caller);
9115 let mut nested_alias = 0usize;
9116 build_condition(
9117 &expression,
9118 &child_scope,
9119 param_idx,
9120 values,
9121 &mut nested_alias,
9122 )?
9123 }
9124 None => None,
9125 };
9126
9127 let child_order = match arguments.get("order_by") {
9128 Some(order) if !order.is_null() => order_terms(
9129 order,
9130 schema_cache,
9131 &plan.foreign_schema,
9132 &plan.foreign_table,
9133 child_alias,
9134 names,
9135 )?,
9136 _ => None,
9137 };
9138
9139 let ceiling = match (
9144 max_rows,
9145 crate::role::read_limit(caller, names, &plan.foreign_schema, &plan.foreign_table),
9146 ) {
9147 (Some(configured), Some(granted)) => Some(configured.min(granted)),
9148 (only, None) | (None, only) => only,
9149 };
9150 let child_limit = match arguments.get("limit").and_then(|v| v.as_i64()) {
9151 Some(requested) => match ceiling {
9152 Some(ceiling) => Some(requested.min(ceiling)),
9153 None => Some(requested),
9154 },
9155 None => ceiling,
9156 };
9157 let child_offset = arguments
9158 .get("offset")
9159 .and_then(|v| v.as_i64())
9160 .unwrap_or(0);
9161
9162 let requested = match arguments.get("distinct_on") {
9166 Some(serde_json::Value::Array(items)) => items
9167 .iter()
9168 .filter_map(|item| item.as_str())
9169 .collect::<Vec<_>>(),
9170 Some(serde_json::Value::String(one)) => vec![one.as_str()],
9171 _ => Vec::new(),
9172 };
9173 let mut distinct_on = Vec::with_capacity(requested.len());
9174 if !requested.is_empty() {
9175 let qi = postrust_core::api_request::QualifiedIdentifier::new(
9176 &plan.foreign_schema,
9177 &plan.foreign_table,
9178 );
9179 let table = schema_cache.get_table(&qi).ok_or_else(|| {
9180 async_graphql::Error::new(format!("unknown table \"{}\"", plan.foreign_table))
9181 })?;
9182 for field in requested {
9183 let column = table_column_for(names, table, field).to_string();
9184 if table.get_column(&column).is_none() {
9185 return Err(async_graphql::Error::new(format!(
9186 "cannot take distinct on unknown column \"{}\" of \"{}\"",
9187 field, plan.foreign_table
9188 )));
9189 }
9190 distinct_on.push(format!(
9191 "{}.{}",
9192 postrust_sql::escape_ident(child_alias),
9193 postrust_sql::escape_ident(&column)
9194 ));
9195 }
9196 }
9197 let (distinct, child_order) = distinct_on_clause(&distinct_on, child_order.as_deref())?;
9198
9199 Ok((
9200 distinct,
9201 child_where,
9202 child_order,
9203 child_limit,
9204 child_offset,
9205 ))
9206}
9207
9208#[allow(clippy::too_many_arguments)] fn build_embed_expressions<'a>(
9210 caller: &crate::role::Caller<'_>,
9211 schema_cache: &SchemaCache,
9212 relationships: &HashMap<String, Vec<RelationshipField>>,
9213 type_name: &str,
9214 parent_alias: &str,
9215 fields: impl IntoIterator<Item = SelectionField<'a>>,
9216 max_rows: Option<i64>,
9217 alias_counter: &mut usize,
9218 param_idx: &mut usize,
9219 values: &mut Vec<serde_json::Value>,
9220 names: &crate::names::NameOverrides,
9221) -> Result<Vec<(String, String)>, async_graphql::Error> {
9222 let Some(available) = relationships.get(type_name) else {
9223 return Ok(Vec::new());
9224 };
9225
9226 let mut expressions = Vec::new();
9227
9228 for field in fields {
9229 let aggregate_of = field
9233 .name()
9234 .strip_suffix("_aggregate")
9235 .and_then(|name| available.iter().find(|r| r.name == name && r.is_list));
9236
9237 if let Some(rel) = aggregate_of {
9238 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9239 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9240 *alias_counter += 1;
9241 let child_alias = format!("a{}", alias_counter);
9242
9243 let arguments = embed_arguments(field);
9247 let (child_distinct, child_where, child_order, child_limit, child_offset) =
9248 embed_narrowing(
9249 &arguments,
9250 &plan,
9251 schema_cache,
9252 relationships,
9253 &rel.target_type,
9254 &child_alias,
9255 max_rows,
9256 param_idx,
9257 values,
9258 names,
9259 caller,
9260 )?;
9261
9262 let child_table =
9263 schema_cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
9264 &plan.foreign_schema,
9265 &plan.foreign_table,
9266 ));
9267 let select = nested_aggregate_select(
9268 caller,
9269 field,
9270 &child_alias,
9271 schema_cache,
9272 relationships,
9273 &rel.target_type,
9274 child_table,
9275 max_rows,
9276 alias_counter,
9277 param_idx,
9278 values,
9279 names,
9280 )?;
9281
9282 let call = computed_arguments(
9283 rel,
9284 &plan,
9285 &postrust_sql::escape_ident(parent_alias),
9286 arguments.get("args"),
9287 param_idx,
9288 values,
9289 schema_cache,
9290 )?;
9291 let expression = plan
9292 .aggregate_expression(
9293 parent_alias,
9294 &postrust_sql::escape_ident(parent_alias),
9295 &child_alias,
9296 &select,
9297 child_limit,
9298 child_offset,
9299 child_where.as_deref(),
9300 child_order.as_deref(),
9301 call.as_deref(),
9302 &child_distinct,
9303 postrust_core::EmbedRendering::Verbatim,
9307 )
9308 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9309 expressions.push((field.name().to_string(), expression));
9310 continue;
9311 }
9312
9313 let Some(rel) = available.iter().find(|r| r.name == field.name()) else {
9314 continue;
9315 };
9316
9317 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9318 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9319
9320 *alias_counter += 1;
9321 let child_alias = format!("e{}", alias_counter);
9322
9323 let nested = build_embed_expressions(
9324 caller,
9325 schema_cache,
9326 relationships,
9327 &rel.target_type,
9328 &child_alias,
9329 field.selection_set(),
9330 max_rows,
9331 alias_counter,
9332 param_idx,
9333 values,
9334 names,
9335 )?;
9336
9337 let arguments = embed_arguments(field);
9339 let (child_distinct, child_where, child_order, child_limit, child_offset) =
9340 embed_narrowing(
9341 &arguments,
9342 &plan,
9343 schema_cache,
9344 relationships,
9345 &rel.target_type,
9346 &child_alias,
9347 max_rows,
9348 param_idx,
9349 values,
9350 names,
9351 caller,
9352 )?;
9353
9354 let child_relationships = relationships.get(&rel.target_type);
9357 let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
9358 &plan.foreign_schema,
9359 &plan.foreign_table,
9360 );
9361 let child_table = schema_cache.get_table(&child_qi);
9362 let mut parts: Vec<String> = Vec::new();
9363 for sub in field.selection_set() {
9364 let name = sub.name();
9365 let is_relationship = child_relationships
9366 .map(|rels| {
9367 rels.iter().any(|r| {
9368 r.name == name || (r.is_list && format!("{}_aggregate", r.name) == name)
9369 })
9370 })
9371 .unwrap_or(false);
9372 if is_relationship {
9373 continue;
9374 }
9375 let column = child_table
9378 .map(|t| table_column_for(names, t, name))
9379 .unwrap_or(name);
9380 let computed = child_table
9381 .filter(|t| t.get_column(column).is_none())
9382 .and_then(|t| {
9383 let function = names
9384 .computed_source(&t.schema, &t.name, name)
9385 .unwrap_or(name);
9386 t.get_computed_column(function)
9387 });
9388 match computed {
9389 Some(definition) => parts.push(format!(
9390 "{} AS {}",
9391 computed_call(
9392 definition,
9393 &format!("{}.*", postrust_sql::escape_ident(&child_alias)),
9394 &postrust_sql::escape_ident(&child_alias),
9395 ),
9396 postrust_sql::escape_ident(name)
9397 )),
9398 None if column != name => parts.push(format!(
9401 "{} AS {}",
9402 postrust_sql::escape_ident(column),
9403 postrust_sql::escape_ident(name)
9404 )),
9405 None => parts.push(postrust_sql::escape_ident(name)),
9406 }
9407 }
9408 if parts.is_empty() && nested.is_empty() {
9409 parts.push(
9410 child_table
9411 .and_then(|table| rename_projection(table, &child_alias, names))
9412 .unwrap_or_else(|| format!("{}.*", postrust_sql::escape_ident(&child_alias))),
9413 );
9414 }
9415 for (field_name, expression) in nested {
9416 parts.push(format!(
9417 "{} AS {}",
9418 expression,
9419 postrust_sql::escape_ident(&field_name)
9420 ));
9421 }
9422
9423 let computed_call_arguments = computed_arguments(
9424 rel,
9425 &plan,
9426 &postrust_sql::escape_ident(parent_alias),
9427 arguments.get("args"),
9428 param_idx,
9429 values,
9430 schema_cache,
9431 )?;
9432 let expression = plan
9433 .embed_expression(
9434 parent_alias,
9435 &postrust_sql::escape_ident(parent_alias),
9439 &child_alias,
9440 &format!("{}{}", child_distinct, parts.join(", ")),
9441 child_limit,
9442 child_offset,
9443 child_where.as_deref(),
9444 child_order.as_deref(),
9445 computed_call_arguments.as_deref(),
9446 postrust_core::EmbedRendering::Verbatim,
9447 )
9448 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9449
9450 expressions.push((rel.name.clone(), expression));
9451 }
9452
9453 Ok(expressions)
9454}
9455
9456struct EmbedContext<'c> {
9462 schema_cache: &'c SchemaCache,
9463 relationships: &'c HashMap<String, Vec<RelationshipField>>,
9464 max_rows: Option<i64>,
9465}
9466
9467fn embed_relationships<'f>(
9473 conn: &'f mut sqlx::PgConnection,
9474 ctx: &'f EmbedContext<'f>,
9475 type_name: &'f str,
9476 selection: async_graphql::SelectionField<'f>,
9477 rows: &'f mut [serde_json::Value],
9478) -> futures::future::BoxFuture<'f, Result<(), async_graphql::Error>> {
9479 Box::pin(async move {
9480 if rows.is_empty() {
9481 return Ok(());
9482 }
9483
9484 let Some(available) = ctx.relationships.get(type_name) else {
9485 return Ok(());
9486 };
9487
9488 for requested in selection.selection_set() {
9489 let Some(rel) = available.iter().find(|r| r.name == requested.name()) else {
9490 continue;
9491 };
9492
9493 let plan =
9494 postrust_core::embed::EmbedPlan::resolve(&rel.relationship, ctx.schema_cache)
9495 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9496
9497 let keys = postrust_core::embed::parent_keys(rows, &plan.local_column);
9498
9499 let mut child_columns: Vec<String> = Vec::new();
9509 let mut project_everything = false;
9510 let child_relationships = ctx.relationships.get(&rel.target_type);
9511 for field in requested.selection_set() {
9512 let name = field.name();
9513 match child_relationships.and_then(|rels| rels.iter().find(|r| r.name == name)) {
9514 Some(nested_rel) => {
9515 match postrust_core::embed::EmbedPlan::resolve(
9516 &nested_rel.relationship,
9517 ctx.schema_cache,
9518 ) {
9519 Ok(nested_plan) => child_columns.push(nested_plan.local_column),
9520 Err(_) => project_everything = true,
9521 }
9522 }
9523 None => child_columns.push(name.to_string()),
9524 }
9525 }
9526 if project_everything || child_columns.is_empty() {
9527 child_columns.clear();
9528 }
9529
9530 let mut grouped = if keys.is_empty() {
9531 std::collections::HashMap::new()
9532 } else {
9533 let sql = plan
9534 .children_grouped_sql(ctx.max_rows, &child_columns)
9535 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9536
9537 let fetched = sqlx::query(&sql)
9538 .bind(&keys)
9539 .fetch_all(&mut *conn)
9540 .await
9541 .map_err(database_error)?;
9542
9543 use sqlx::Row;
9546 let pairs: Vec<(serde_json::Value, serde_json::Value)> = fetched
9547 .into_iter()
9548 .filter_map(|row| {
9549 Some((
9550 row.try_get::<serde_json::Value, _>(0).ok()?,
9551 row.try_get::<serde_json::Value, _>(1).ok()?,
9552 ))
9553 })
9554 .collect();
9555
9556 postrust_core::embed::group_from_aggregated(pairs)
9557 };
9558
9559 let has_deeper_embed = ctx
9564 .relationships
9565 .get(&rel.target_type)
9566 .map(|rels| {
9567 requested
9568 .selection_set()
9569 .any(|field| rels.iter().any(|r| r.name == field.name()))
9570 })
9571 .unwrap_or(false);
9572
9573 if has_deeper_embed {
9574 let mut order: Vec<(String, usize)> = Vec::with_capacity(grouped.len());
9575 let mut flat: Vec<serde_json::Value> = Vec::new();
9576 for (key, children) in grouped.drain() {
9577 order.push((key, children.len()));
9578 flat.extend(children);
9579 }
9580
9581 embed_relationships(&mut *conn, ctx, &rel.target_type, requested, &mut flat)
9582 .await?;
9583
9584 let mut rest = flat.into_iter();
9585 for (key, count) in order {
9586 grouped.insert(key, rest.by_ref().take(count).collect());
9587 }
9588 }
9589
9590 for row in rows.iter_mut() {
9591 postrust_core::embed::attach_to_parent(row, &rel.name, &plan, &grouped);
9592 }
9593 }
9594
9595 Ok(())
9596 })
9597}
9598
9599fn pk_where_from_args(
9604 ctx: &ResolverContext<'_>,
9605 schema_name: &str,
9606 table_name: &str,
9607 pk_columns: &[(String, String)],
9608 names: &crate::names::NameOverrides,
9609) -> Result<serde_json::Value, async_graphql::Error> {
9610 if pk_columns.is_empty() {
9611 return Err(async_graphql::Error::new(format!(
9612 "\"{}\" has no primary key, so it cannot be mutated by key",
9613 table_name
9614 )));
9615 }
9616
9617 let from_object = ctx
9621 .args
9622 .try_get("pk_columns")
9623 .ok()
9624 .map(|v| accessor_to_json(&v));
9625
9626 let mut conditions = serde_json::Map::new();
9627 for (col_name, _) in pk_columns {
9628 let field = names
9632 .column(schema_name, table_name, col_name)
9633 .unwrap_or(col_name);
9634 let value = match &from_object {
9635 Some(serde_json::Value::Object(map)) => map.get(field).cloned().ok_or_else(|| {
9636 async_graphql::Error::new(format!(
9637 "pk_columns is missing the key column \"{}\"",
9638 field
9639 ))
9640 })?,
9641 _ => {
9642 let arg = ctx.args.try_get(field).map_err(|_| {
9643 async_graphql::Error::new(format!(
9644 "missing required primary key argument \"{}\"",
9645 field
9646 ))
9647 })?;
9648 accessor_to_json(&arg)
9649 }
9650 };
9651 conditions.insert(field.to_string(), serde_json::json!({ "_eq": value }));
9652 }
9653
9654 Ok(serde_json::Value::Object(conditions))
9655}
9656
9657fn pk_argument_type(pg_type: &str) -> String {
9664 let rendered = crate::types::pg_type_to_graphql(pg_type).to_string();
9665 if rendered.starts_with('[') {
9666 "String".to_string()
9667 } else {
9668 rendered
9669 }
9670}
9671
9672fn exposed_pk_argument_type(
9677 names: &crate::names::NameOverrides,
9678 schema: &str,
9679 table: &str,
9680 column: &str,
9681 pg_type: &str,
9682) -> String {
9683 names
9684 .column_type(schema, table, column)
9685 .map(|given| given.to_string())
9686 .unwrap_or_else(|| pk_argument_type(pg_type))
9687}
9688
9689fn graphql_type_ref(type_str: &str) -> TypeRef {
9691 let is_list = type_str.starts_with('[');
9693 let is_nn = type_str.ends_with('!');
9694
9695 let inner = if is_list {
9697 let stripped = type_str
9698 .trim_end_matches('!') .trim_start_matches('[') .trim_end_matches(']'); stripped
9702 } else {
9703 type_str.trim_end_matches('!')
9704 };
9705
9706 let inner_nn = inner.ends_with('!');
9707 let base_type = inner.trim_end_matches('!');
9708
9709 if is_list {
9710 if is_nn {
9711 if inner_nn {
9712 TypeRef::named_nn_list_nn(base_type)
9713 } else {
9714 TypeRef::named_list_nn(base_type)
9715 }
9716 } else if inner_nn {
9717 TypeRef::named_nn_list(base_type)
9718 } else {
9719 TypeRef::named_list(base_type)
9720 }
9721 } else if is_nn {
9722 TypeRef::named_nn(base_type)
9723 } else {
9724 TypeRef::named(base_type)
9725 }
9726}
9727
9728const NUMBER_AS_TEXT: &str = "$serde_json::private::Number";
9738
9739fn number_kept_as_text(
9742 map: &serde_json::Map<String, serde_json::Value>,
9743) -> Option<serde_json::Value> {
9744 if map.len() != 1 {
9745 return None;
9746 }
9747 let text = map.get(NUMBER_AS_TEXT)?.as_str()?;
9748 serde_json::from_str::<serde_json::Value>(text)
9749 .ok()
9750 .filter(|value| value.is_number())
9751}
9752
9753fn plain_numbers(value: serde_json::Value) -> serde_json::Value {
9755 match value {
9756 serde_json::Value::Object(map) => match number_kept_as_text(&map) {
9757 Some(number) => number,
9758 None => serde_json::Value::Object(
9759 map.into_iter()
9760 .map(|(k, v)| (k, plain_numbers(v)))
9761 .collect(),
9762 ),
9763 },
9764 serde_json::Value::Array(items) => {
9765 serde_json::Value::Array(items.into_iter().map(plain_numbers).collect())
9766 }
9767 other => other,
9768 }
9769}
9770
9771fn accessor_to_json(accessor: &ValueAccessor<'_>) -> serde_json::Value {
9773 if accessor.is_null() {
9775 serde_json::Value::Null
9776 } else if let Ok(b) = accessor.boolean() {
9777 serde_json::Value::Bool(b)
9778 } else if let Ok(i) = accessor.i64() {
9779 serde_json::Value::Number(i.into())
9780 } else if let Ok(f) = accessor.f64() {
9781 serde_json::Number::from_f64(f)
9782 .map(serde_json::Value::Number)
9783 .unwrap_or(serde_json::Value::Null)
9784 } else if let Ok(s) = accessor.string() {
9785 serde_json::Value::String(s.to_string())
9786 } else if let Ok(name) = accessor.enum_name() {
9787 serde_json::Value::String(name.to_string())
9790 } else if let Ok(list) = accessor.list() {
9791 serde_json::Value::Array(list.iter().map(|v| accessor_to_json(&v)).collect())
9792 } else if let Ok(obj) = accessor.object() {
9793 let map: serde_json::Map<String, serde_json::Value> = obj
9794 .iter()
9795 .map(|(k, v)| (k.to_string(), accessor_to_json(&v)))
9796 .collect();
9797 match number_kept_as_text(&map) {
9798 Some(number) => number,
9799 None => serde_json::Value::Object(map),
9800 }
9801 } else {
9802 serde_json::Value::Null
9803 }
9804}
9805
9806fn value_to_json(value: &Value) -> serde_json::Value {
9808 match value {
9809 Value::Null => serde_json::Value::Null,
9810 Value::Boolean(b) => serde_json::Value::Bool(*b),
9811 Value::Number(n) => {
9812 if let Some(i) = n.as_i64() {
9813 serde_json::Value::Number(i.into())
9814 } else if let Some(f) = n.as_f64() {
9815 serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
9816 } else {
9817 serde_json::Value::Null
9818 }
9819 }
9820 Value::String(s) => serde_json::Value::String(s.clone()),
9821 Value::List(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
9822 Value::Object(obj) => {
9823 let map: serde_json::Map<String, serde_json::Value> = obj
9824 .iter()
9825 .map(|(k, v)| (k.to_string(), value_to_json(v)))
9826 .collect();
9827 serde_json::Value::Object(map)
9828 }
9829 Value::Binary(b) => serde_json::Value::String(base64::Engine::encode(
9830 &base64::engine::general_purpose::STANDARD,
9831 b,
9832 )),
9833 Value::Enum(e) => serde_json::Value::String(e.to_string()),
9834 }
9835}
9836
9837fn json_to_value(json: serde_json::Value) -> Value {
9839 match json {
9840 serde_json::Value::Null => Value::Null,
9841 serde_json::Value::Bool(b) => Value::Boolean(b),
9842 serde_json::Value::Number(n) => {
9843 if let Some(i) = n.as_i64() {
9844 Value::Number(i.into())
9845 } else if let Some(f) = n.as_f64() {
9846 Value::Number(async_graphql::Number::from_f64(f).unwrap())
9847 } else {
9848 Value::Null
9849 }
9850 }
9851 serde_json::Value::String(s) => Value::String(s),
9852 serde_json::Value::Array(arr) => Value::List(arr.into_iter().map(json_to_value).collect()),
9853 serde_json::Value::Object(obj) => {
9854 let map: indexmap::IndexMap<async_graphql::Name, Value> = obj
9855 .into_iter()
9856 .map(|(k, v)| (async_graphql::Name::new(k), json_to_value(v)))
9857 .collect();
9858 Value::Object(map)
9859 }
9860 }
9861}
9862
9863#[cfg(test)]
9873mod tests {
9874 use super::*;
9875 use indexmap::IndexMap;
9876 use postrust_core::schema_cache::{Cardinality, Column, Relationship, Table};
9877 use postrust_core::QualifiedIdentifier;
9878 use std::collections::{HashMap, HashSet};
9879
9880 fn entity_key_column(pg_type: &str, explicit_id: bool) -> EntityKeyColumn {
9881 EntityKeyColumn {
9882 field_name: "id".to_string(),
9883 column_name: "id".to_string(),
9884 pg_type: pg_type.to_string(),
9885 explicit_id,
9886 }
9887 }
9888
9889 #[test]
9890 fn federation_integer_keys_reject_strings() {
9891 let column = entity_key_column("int4", false);
9892
9893 assert_eq!(
9894 normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap(),
9895 serde_json::json!(2)
9896 );
9897 let error = normalize_entity_key_value("users", &column, &serde_json::json!("2"))
9898 .expect_err("a GraphQL Int cannot be a string");
9899 assert_eq!(
9900 error.message,
9901 "entity representation for \"users\" has invalid key field \"id\": expected a value compatible with PostgreSQL type \"int4\", found a string"
9902 );
9903 }
9904
9905 #[test]
9906 fn federation_bigint_keys_accept_exact_decimal_strings() {
9907 let column = entity_key_column("int8", false);
9908
9909 assert_eq!(
9910 normalize_entity_key_value("users", &column, &serde_json::json!(7)).unwrap(),
9911 serde_json::json!(7)
9912 );
9913 assert_eq!(
9914 normalize_entity_key_value("users", &column, &serde_json::json!("9007199254740993"))
9915 .unwrap(),
9916 serde_json::json!(9_007_199_254_740_993_i64)
9917 );
9918 assert!(normalize_entity_key_value(
9919 "users",
9920 &column,
9921 &serde_json::json!("9223372036854775808")
9922 )
9923 .is_err());
9924 assert!(normalize_entity_key_value("users", &column, &serde_json::json!("7.5")).is_err());
9925 }
9926
9927 #[test]
9928 fn federation_numeric_keys_accept_exact_decimal_strings() {
9929 let column = entity_key_column("numeric", false);
9930
9931 assert_eq!(
9932 normalize_entity_key_value("users", &column, &serde_json::json!(7.5)).unwrap(),
9933 serde_json::json!("7.5")
9934 );
9935 assert_eq!(
9936 normalize_entity_key_value(
9937 "users",
9938 &column,
9939 &serde_json::json!("9007199254740993.123456789")
9940 )
9941 .unwrap(),
9942 serde_json::json!("9007199254740993.123456789")
9943 );
9944 assert!(
9945 normalize_entity_key_value("users", &column, &serde_json::json!("not-a-number"))
9946 .is_err()
9947 );
9948 }
9949
9950 #[test]
9951 fn federation_integer_backed_ids_have_one_normal_form() {
9952 let column = entity_key_column("int4", true);
9953
9954 let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
9955 let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
9956 assert_eq!(number, serde_json::json!(2));
9957 assert_eq!(string, number);
9958 assert!(normalize_entity_key_value("users", &column, &serde_json::json!("two")).is_err());
9959 }
9960
9961 #[test]
9962 fn federation_text_backed_ids_normalize_integer_input_to_text() {
9963 let column = entity_key_column("text", true);
9964
9965 let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
9966 let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
9967 assert_eq!(number, serde_json::json!("2"));
9968 assert_eq!(string, number);
9969 }
9970
9971 #[test]
9972 fn federation_uuid_backed_ids_are_validated_and_canonicalized() {
9973 let column = entity_key_column("uuid", true);
9974
9975 assert_eq!(
9976 normalize_entity_key_value(
9977 "users",
9978 &column,
9979 &serde_json::json!("550E8400-E29B-41D4-A716-446655440000")
9980 )
9981 .unwrap(),
9982 serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
9983 );
9984 assert!(
9985 normalize_entity_key_value("users", &column, &serde_json::json!("not-a-uuid")).is_err()
9986 );
9987 }
9988
9989 fn create_test_table(name: &str) -> Table {
9990 let mut columns = IndexMap::new();
9991 columns.insert(
9992 "id".into(),
9993 Column {
9994 name: "id".into(),
9995 description: None,
9996 nullable: false,
9997 data_type: "integer".into(),
9998 nominal_type: "int4".into(),
9999 max_len: None,
10000 default: Some("nextval('id_seq')".into()),
10001 enum_values: vec![],
10002 is_pk: true,
10003 position: 1,
10004 always_generated: false,
10005 domain_type: None,
10006 },
10007 );
10008 columns.insert(
10009 "name".into(),
10010 Column {
10011 name: "name".into(),
10012 description: None,
10013 nullable: false,
10014 data_type: "text".into(),
10015 nominal_type: "text".into(),
10016 max_len: None,
10017 default: None,
10018 enum_values: vec![],
10019 is_pk: false,
10020 position: 2,
10021 always_generated: false,
10022 domain_type: None,
10023 },
10024 );
10025
10026 Table {
10027 schema: "public".into(),
10028 name: name.into(),
10029 description: None,
10030 is_view: false,
10031 insertable: true,
10032 updatable: true,
10033 deletable: true,
10034 pk_cols: vec!["id".into()],
10035 unique_constraints: Vec::new(),
10036 columns,
10037 computed_columns: Default::default(),
10038 is_partitioned: false,
10039 }
10040 }
10041
10042 fn create_test_schema_cache() -> SchemaCache {
10043 let mut tables = HashMap::new();
10044 let users = create_test_table("users");
10045 tables.insert(users.qualified_identifier(), users);
10046
10047 SchemaCache {
10048 tables,
10049 relationships: HashMap::new(),
10050 routines: HashMap::new(),
10051 timezones: HashSet::new(),
10052 media_handlers: HashMap::new(),
10053 pg_version: 150000,
10054 representations: Default::default(),
10055 }
10056 }
10057
10058 fn add_test_table(cache: &mut SchemaCache, name: &str) {
10059 let table = create_test_table(name);
10060 cache.tables.insert(table.qualified_identifier(), table);
10061 }
10062
10063 fn add_test_posts_relationship(cache: &mut SchemaCache) {
10064 let users = QualifiedIdentifier::new("public", "users");
10065 cache.relationships.insert(
10066 (users.clone(), "public".into()),
10067 vec![Relationship::ForeignKey {
10068 table: users,
10069 foreign_table: QualifiedIdentifier::new("public", "posts"),
10070 is_self: false,
10071 cardinality: Cardinality::O2M {
10072 constraint: "posts_user_id_fkey".into(),
10073 columns: vec![("id".into(), "user_id".into())],
10074 },
10075 table_is_view: false,
10076 foreign_table_is_view: false,
10077 constraint_name: "posts_user_id_fkey".into(),
10078 }],
10079 );
10080 }
10081
10082 #[test]
10087 fn test_graphql_type_ref_simple() {
10088 let _type_ref = graphql_type_ref("String");
10089 }
10091
10092 #[test]
10093 fn test_graphql_type_ref_non_null() {
10094 let _type_ref = graphql_type_ref("String!");
10095 }
10096
10097 #[test]
10098 fn test_graphql_type_ref_list() {
10099 let _type_ref = graphql_type_ref("[String]");
10100 }
10101
10102 #[test]
10103 fn test_graphql_type_ref_list_non_null() {
10104 let _type_ref = graphql_type_ref("[String!]!");
10105 }
10106
10107 #[test]
10112 fn test_value_to_json_null() {
10113 let value = Value::Null;
10114 let json = value_to_json(&value);
10115 assert_eq!(json, serde_json::Value::Null);
10116 }
10117
10118 #[test]
10119 fn test_value_to_json_boolean() {
10120 let value = Value::Boolean(true);
10121 let json = value_to_json(&value);
10122 assert_eq!(json, serde_json::Value::Bool(true));
10123 }
10124
10125 #[test]
10126 fn test_value_to_json_number() {
10127 let value = Value::Number(42.into());
10128 let json = value_to_json(&value);
10129 assert_eq!(json, serde_json::json!(42));
10130 }
10131
10132 #[test]
10133 fn test_value_to_json_string() {
10134 let value = Value::String("hello".to_string());
10135 let json = value_to_json(&value);
10136 assert_eq!(json, serde_json::Value::String("hello".to_string()));
10137 }
10138
10139 #[test]
10140 fn test_value_to_json_list() {
10141 let value = Value::List(vec![Value::Number(1.into()), Value::Number(2.into())]);
10142 let json = value_to_json(&value);
10143 assert_eq!(json, serde_json::json!([1, 2]));
10144 }
10145
10146 #[test]
10147 fn test_json_to_value_null() {
10148 let json = serde_json::Value::Null;
10149 let value = json_to_value(json);
10150 assert!(matches!(value, Value::Null));
10151 }
10152
10153 #[test]
10154 fn test_json_to_value_boolean() {
10155 let json = serde_json::Value::Bool(false);
10156 let value = json_to_value(json);
10157 assert!(matches!(value, Value::Boolean(false)));
10158 }
10159
10160 #[test]
10161 fn test_json_to_value_number() {
10162 let json = serde_json::json!(123);
10163 let value = json_to_value(json);
10164 assert!(matches!(value, Value::Number(_)));
10165 }
10166
10167 #[test]
10168 fn test_json_to_value_string() {
10169 let json = serde_json::Value::String("test".to_string());
10170 let value = json_to_value(json);
10171 assert!(matches!(value, Value::String(_)));
10172 }
10173
10174 #[test]
10175 fn test_json_to_value_array() {
10176 let json = serde_json::json!([1, 2, 3]);
10177 let value = json_to_value(json);
10178 assert!(matches!(value, Value::List(_)));
10179 }
10180
10181 #[test]
10182 fn test_json_to_value_object() {
10183 let json = serde_json::json!({"key": "value"});
10184 let value = json_to_value(json);
10185 assert!(matches!(value, Value::Object(_)));
10186 }
10187
10188 #[test]
10193 fn test_build_dynamic_schema() {
10194 let cache = create_test_schema_cache();
10195 let config = SchemaConfig::default();
10196 let generated = build_schema(&cache, &config);
10197
10198 let result = build_dynamic_schema(
10199 &generated,
10200 &cache,
10201 None,
10202 None,
10203 Arc::new(Default::default()),
10204 std::time::Duration::from_secs(30),
10205 None,
10206 );
10207 if let Err(ref e) = result {
10208 eprintln!("Schema build error: {:?}", e);
10209 }
10210 assert!(result.is_ok(), "Schema build failed: {:?}", result.err());
10211 }
10212
10213 #[test]
10214 fn a_column_type_override_reaches_fields_and_key_arguments() {
10215 let cache = create_test_schema_cache();
10216 let names = crate::names::NameOverrides::parse(
10217 r#"{"tables": {"public.users": {"column_types": {"id": "ID"}}}}"#,
10218 )
10219 .unwrap();
10220 let config = SchemaConfig {
10221 names: names.clone(),
10222 ..SchemaConfig::default()
10223 };
10224 let generated = build_schema(&cache, &config);
10225 let schema = build_dynamic_schema(
10226 &generated,
10227 &cache,
10228 None,
10229 None,
10230 Arc::new(names),
10231 std::time::Duration::from_secs(30),
10232 None,
10233 )
10234 .expect("schema with an ID override should build");
10235 let sdl = schema.sdl();
10236
10237 let input_body = |name: &str| {
10238 sdl.split_once(&format!("input {name} {{"))
10239 .and_then(|(_, rest)| rest.split_once('}'))
10240 .map(|(body, _)| body.to_string())
10241 .unwrap_or_else(|| panic!("missing input {name}:\n{sdl}"))
10242 };
10243
10244 assert!(sdl.contains("id: ID!"), "object and input fields:\n{sdl}");
10245 assert!(
10246 sdl.contains("users_by_pk(id: ID!): users"),
10247 "query key argument:\n{sdl}"
10248 );
10249 assert!(
10250 sdl.contains("delete_users_by_pk(id: ID!): users"),
10251 "delete key argument:\n{sdl}"
10252 );
10253 assert!(
10254 input_body("users_pk_columns_input").contains("id: ID!"),
10255 "update key input:\n{sdl}"
10256 );
10257 assert!(
10258 input_body("users_bool_exp").contains("id: ID_comparison_exp"),
10259 "comparison input:\n{sdl}"
10260 );
10261 assert!(
10262 input_body("users_insert_input").contains("id: ID"),
10263 "write input:\n{sdl}"
10264 );
10265 }
10266
10267 #[test]
10268 fn a_type_prefix_names_the_complete_dynamic_schema() {
10269 let cache = create_test_schema_cache();
10270 let config = SchemaConfig {
10271 type_prefix: Some("test".into()),
10272 ..SchemaConfig::default()
10273 };
10274 let generated = build_schema(&cache, &config);
10275 let schema = build_dynamic_schema(
10276 &generated,
10277 &cache,
10278 None,
10279 None,
10280 Arc::new(Default::default()),
10281 std::time::Duration::from_secs(30),
10282 None,
10283 )
10284 .expect("a prefixed schema should build");
10285 let sdl = schema.sdl();
10286
10287 assert!(sdl.contains("type test_users "), "object type:\n{sdl}");
10288 assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10289 assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10290 assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10291 assert!(
10292 sdl.contains("test_users_aggregate"),
10293 "aggregate type:\n{sdl}"
10294 );
10295 assert!(sdl.contains("test_insert_users("), "mutation root:\n{sdl}");
10296 }
10297
10298 #[test]
10299 fn federation_is_absent_by_default() {
10300 let cache = create_test_schema_cache();
10301 let generated = build_schema(&cache, &SchemaConfig::default());
10302 let schema = build_dynamic_schema(
10303 &generated,
10304 &cache,
10305 None,
10306 None,
10307 Arc::new(Default::default()),
10308 std::time::Duration::from_secs(30),
10309 None,
10310 )
10311 .expect("a non-federated schema should build");
10312 let sdl = schema.sdl();
10313
10314 assert!(!sdl.contains("_Service"), "service type:\n{sdl}");
10315 assert!(!sdl.contains("_service"), "service field:\n{sdl}");
10316 assert!(!sdl.contains("@key"), "federation key:\n{sdl}");
10317 }
10318
10319 #[test]
10320 fn federation_entity_selections_respect_fragment_type_conditions() {
10321 let document = async_graphql::parser::parse_query(
10322 r#"
10323 query {
10324 _entities(representations: []) {
10325 __typename
10326 ... on authors { id items { title } }
10327 ...ShopFields
10328 ... { common }
10329 ...EntityName
10330 }
10331 }
10332 fragment ShopFields on shops { id items { sku } }
10333 fragment EntityName on _Entity { __typename }
10334 "#,
10335 )
10336 .expect("query parses");
10337 let (_, operation) = document.operations.iter().next().expect("one operation");
10338 let entity = operation
10339 .node
10340 .selection_set
10341 .node
10342 .items
10343 .iter()
10344 .find_map(|selection| match &selection.node {
10345 Selection::Field(field) if field.node.name.node == "_entities" => Some(&field.node),
10346 _ => None,
10347 })
10348 .expect("_entities field");
10349
10350 assert_eq!(
10351 entity_field_applicability(&entity.selection_set.node, &document.fragments, "authors"),
10352 vec![true, true, true, false, false, true, true]
10353 );
10354 assert_eq!(
10355 entity_field_applicability(&entity.selection_set.node, &document.fragments, "shops"),
10356 vec![true, false, false, true, true, true, true]
10357 );
10358 }
10359
10360 #[tokio::test]
10361 async fn federation_entity_lists_allow_null_members() {
10362 let entity =
10363 Object::new("widgets").field(Field::new("id", TypeRef::named_nn(TypeRef::INT), |_| {
10364 FieldFuture::new(async { Ok(Some(Value::from(2))) })
10365 }));
10366 let union = Union::new("_Entity").possible_type("widgets");
10367 let query = Object::new("Query").field(Field::new(
10368 "_entities",
10369 TypeRef::named_list_nn("_Entity"),
10370 |_| {
10371 FieldFuture::new(async {
10372 Ok(Some(FieldValue::list([
10373 FieldValue::NULL,
10374 FieldValue::NULL.with_type("widgets"),
10375 ])))
10376 })
10377 },
10378 ));
10379 let schema = Schema::build("Query", None, None)
10380 .extension(NullableFederationEntity)
10381 .register(entity)
10382 .register(union)
10383 .register(query)
10384 .finish()
10385 .expect("schema builds");
10386
10387 let response = schema
10388 .execute("{ _entities { ... on widgets { id } } }")
10389 .await;
10390
10391 assert!(response.errors.is_empty(), "{:?}", response.errors);
10392 assert_eq!(
10393 response.data,
10394 async_graphql::value!({ "_entities": [null, { "id": 2 }] })
10395 );
10396 }
10397
10398 #[test]
10399 fn federation_metadata_is_byte_for_byte_inert_when_disabled() {
10400 let cache = create_test_schema_cache();
10401 let sdl_for = |config: SchemaConfig| {
10402 let generated = build_schema(&cache, &config);
10403 build_dynamic_schema(
10404 &generated,
10405 &cache,
10406 None,
10407 None,
10408 Arc::new(config.names.clone()),
10409 std::time::Duration::from_secs(30),
10410 None,
10411 )
10412 .expect("schema should build")
10413 .sdl()
10414 };
10415
10416 let baseline = sdl_for(SchemaConfig {
10417 type_prefix: Some("test".into()),
10418 ..SchemaConfig::default()
10419 });
10420 let with_disabled_federation_metadata = sdl_for(SchemaConfig {
10421 enable_federation: false,
10422 type_prefix: Some("test".into()),
10423 names: crate::names::NameOverrides::parse(
10424 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10425 )
10426 .expect("metadata"),
10427 ..SchemaConfig::default()
10428 });
10429
10430 assert_eq!(with_disabled_federation_metadata, baseline);
10431 assert!(
10432 with_disabled_federation_metadata.contains("type test_users "),
10433 "prefixed object type remains when federation is disabled:\n{with_disabled_federation_metadata}"
10434 );
10435 assert!(
10436 !with_disabled_federation_metadata.contains("_Service"),
10437 "service type:\n{with_disabled_federation_metadata}"
10438 );
10439 assert!(
10440 !with_disabled_federation_metadata.contains("@key"),
10441 "federation key:\n{with_disabled_federation_metadata}"
10442 );
10443 }
10444
10445 #[test]
10446 fn shared_entities_make_relationship_fields_shareable() {
10447 let federation_sdl = |shared: bool| {
10448 let mut cache = create_test_schema_cache();
10449 add_test_table(&mut cache, "posts");
10450 add_test_posts_relationship(&mut cache);
10451 let names = match shared {
10452 true => crate::names::NameOverrides::parse(
10453 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10454 )
10455 .expect("metadata"),
10456 false => crate::names::NameOverrides::default(),
10457 };
10458 let config = SchemaConfig {
10459 enable_federation: true,
10460 type_prefix: Some("test".into()),
10461 names: names.clone(),
10462 ..SchemaConfig::default()
10463 };
10464 let generated = build_schema(&cache, &config);
10465 build_dynamic_schema(
10466 &generated,
10467 &cache,
10468 None,
10469 None,
10470 Arc::new(names),
10471 std::time::Duration::from_secs(30),
10472 None,
10473 )
10474 .expect("federated schema should build")
10475 .sdl_with_options(async_graphql::SDLExportOptions::new().federation())
10476 };
10477
10478 let shared = federation_sdl(true);
10479 assert!(
10480 shared.contains("): [test_posts!]! @shareable"),
10481 "shared relationship field:\n{shared}"
10482 );
10483 assert!(
10484 shared.contains("): test_posts_aggregate! @shareable"),
10485 "shared relationship aggregate field:\n{shared}"
10486 );
10487
10488 let not_shared = federation_sdl(false);
10489 assert!(
10490 not_shared.contains("): [test_posts!]!"),
10491 "non-shared relationship field:\n{not_shared}"
10492 );
10493 assert!(
10494 !not_shared.contains("): [test_posts!]! @shareable"),
10495 "non-shared relationship field must not be shareable:\n{not_shared}"
10496 );
10497 assert!(
10498 not_shared.contains("): test_posts_aggregate!"),
10499 "non-shared relationship aggregate field:\n{not_shared}"
10500 );
10501 assert!(
10502 !not_shared.contains("): test_posts_aggregate! @shareable"),
10503 "non-shared relationship aggregate must not be shareable:\n{not_shared}"
10504 );
10505 }
10506
10507 #[tokio::test]
10508 async fn federation_exposes_service_sdl_and_entity_keys() {
10509 let mut cache = create_test_schema_cache();
10510 add_test_table(&mut cache, "posts");
10511 let config = SchemaConfig {
10512 enable_federation: true,
10513 type_prefix: Some("test".into()),
10514 names: crate::names::NameOverrides::parse(
10515 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10516 )
10517 .expect("metadata"),
10518 ..SchemaConfig::default()
10519 };
10520 let generated = build_schema(&cache, &config);
10521 let schema = build_dynamic_schema(
10522 &generated,
10523 &cache,
10524 None,
10525 None,
10526 Arc::new(Default::default()),
10527 std::time::Duration::from_secs(30),
10528 None,
10529 )
10530 .expect("a federated schema should build");
10531 let sdl = schema.sdl();
10532 let federation_sdl =
10533 schema.sdl_with_options(async_graphql::SDLExportOptions::new().federation());
10534
10535 assert!(sdl.contains("_Service"), "service type:\n{sdl}");
10536 assert!(sdl.contains("_service"), "service field:\n{sdl}");
10537 assert!(
10538 federation_sdl.contains("type users @key(fields: \"id\")"),
10539 "users key:\n{federation_sdl}"
10540 );
10541 assert!(
10542 federation_sdl.contains("@shareable"),
10543 "shared field directive:\n{federation_sdl}"
10544 );
10545 assert!(sdl.contains("type users "), "shared object type:\n{sdl}");
10546 assert!(
10547 !sdl.contains("type test_users "),
10548 "prefixed shared object type:\n{sdl}"
10549 );
10550 assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10551 assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10552 assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10553 assert!(
10554 !sdl.contains("input users_bool_exp "),
10555 "unprefixed derived input:\n{sdl}"
10556 );
10557 assert!(
10558 sdl.contains("test_users_aggregate"),
10559 "aggregate type:\n{sdl}"
10560 );
10561 assert!(
10562 !sdl.contains("type users_aggregate "),
10563 "unprefixed aggregate type:\n{sdl}"
10564 );
10565 assert!(
10566 sdl.contains("test_users_mutation_response"),
10567 "mutation response type:\n{sdl}"
10568 );
10569 assert!(
10570 !sdl.contains("type users_mutation_response "),
10571 "unprefixed mutation response type:\n{sdl}"
10572 );
10573 assert!(
10574 sdl.contains("type test_posts {"),
10575 "non-shared object type remains prefixed:\n{sdl}"
10576 );
10577 assert!(
10578 !sdl.contains("type posts {"),
10579 "non-shared object type must not become unprefixed:\n{sdl}"
10580 );
10581 assert!(sdl.contains("test_posts("), "prefixed posts root:\n{sdl}");
10582 assert!(
10583 sdl.contains("test_posts_bool_exp"),
10584 "prefixed posts bool_exp:\n{sdl}"
10585 );
10586
10587 let response = schema.execute("{ _service { sdl } }").await;
10588 assert!(
10589 response.errors.is_empty(),
10590 "service query errors: {:?}",
10591 response.errors
10592 );
10593 let service = response.data.into_json().expect("service response JSON");
10594 let service_sdl = service
10595 .get("_service")
10596 .and_then(|value| value.get("sdl"))
10597 .and_then(serde_json::Value::as_str)
10598 .expect("service SDL string");
10599 assert!(
10600 service_sdl.contains("type Query"),
10601 "federation root type:\n{service_sdl}"
10602 );
10603 assert!(
10604 service_sdl.contains("test_users("),
10605 "query fields are on the federation root:\n{service_sdl}"
10606 );
10607 assert!(
10608 service_sdl.contains("test_posts("),
10609 "non-shared query fields are on the federation root:\n{service_sdl}"
10610 );
10611 assert!(
10612 !service_sdl.contains("type query_root"),
10613 "Apollo composition treats query_root as an ordinary object without a schema definition:\n{service_sdl}"
10614 );
10615 assert!(
10616 service_sdl.contains("type users @key(fields: \"id\")"),
10617 "{service_sdl}"
10618 );
10619 assert!(
10620 service_sdl.contains("type test_posts @key(fields: \"id\")"),
10621 "{service_sdl}"
10622 );
10623 }
10624
10625 #[test]
10628 fn exists_is_a_subselect_over_another_table() {
10629 let cache = create_test_schema_cache();
10630 let names = crate::names::NameOverrides::default();
10631 let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10632 let scope = WhereScope::table("public", "users", "users", &names)
10633 .with_resolution(&cache, &relationships);
10634
10635 let mut param_idx = 1usize;
10636 let mut values: Vec<serde_json::Value> = Vec::new();
10637 let mut aliases = 0usize;
10638 let sql = build_condition(
10639 &serde_json::json!({
10640 "_exists": {"_table": "users", "_where": {"name": {"_eq": "Ann"}}}
10641 }),
10642 &scope,
10643 &mut param_idx,
10644 &mut values,
10645 &mut aliases,
10646 )
10647 .expect("compiles")
10648 .expect("constrains something");
10649
10650 assert_eq!(
10651 sql,
10652 "EXISTS (SELECT 1 FROM \"public\".\"users\" AS \"pgrst_exists_1\" \
10653 WHERE \"pgrst_exists_1\".\"name\" = $1::text)"
10654 );
10655 assert_eq!(values, vec![serde_json::json!("Ann")]);
10656 }
10657
10658 #[test]
10660 fn exists_names_a_table_the_server_has() {
10661 let cache = create_test_schema_cache();
10662 let names = crate::names::NameOverrides::default();
10663 let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10664 let scope = WhereScope::table("public", "users", "users", &names)
10665 .with_resolution(&cache, &relationships);
10666 let error = build_condition(
10667 &serde_json::json!({"_exists": {"_table": "nowhere", "_where": {}}}),
10668 &scope,
10669 &mut 1,
10670 &mut Vec::new(),
10671 &mut 0,
10672 )
10673 .expect_err("a table that is not there is an error");
10674 assert!(
10675 error.message.contains("public.nowhere"),
10676 "{}",
10677 error.message
10678 );
10679 }
10680
10681 #[test]
10688 fn a_table_a_role_may_count_and_not_read_has_a_count_and_no_rows() {
10689 let cache = create_test_schema_cache();
10690 let names = crate::names::NameOverrides::parse(
10691 r#"{"tables": {"public.users": {"permissions":
10692 {"counter": {"select": {"columns": [], "filter": {},
10693 "allow_aggregations": true}}}}}}"#,
10694 )
10695 .unwrap();
10696 let view = crate::role::cache_for_role(&cache, &names, "counter", false);
10697 assert!(!view.tables.is_empty(), "the table is there to be counted");
10698
10699 let config = SchemaConfig {
10700 names: names.clone(),
10701 role: Some("counter".to_string()),
10702 ..SchemaConfig::default()
10703 };
10704 let generated = build_schema(&view, &config);
10705 let schema = build_dynamic_schema(
10706 &generated,
10707 &view,
10708 None,
10709 None,
10710 Arc::new(names),
10711 std::time::Duration::from_secs(30),
10712 Some("counter"),
10713 )
10714 .expect("a role that may only count still has a buildable schema");
10715 let sdl = schema.sdl();
10716
10717 assert!(
10718 sdl.contains("users_aggregate"),
10719 "the count is there:\n{}",
10720 sdl
10721 );
10722 assert!(sdl.contains("count: Int!"), "and it is a count:\n{}", sdl);
10723 assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10725 assert!(
10726 !sdl.contains("nodes"),
10727 "and no rows beside the numbers:\n{}",
10728 sdl
10729 );
10730 assert!(
10731 !sdl.contains("users_select_column"),
10732 "no column enum to order by or count over:\n{}",
10733 sdl
10734 );
10735 assert!(!sdl.contains("users_max_fields"), "no max:\n{}", sdl);
10737 }
10738
10739 #[test]
10746 fn a_table_a_role_may_only_write_gets_the_write_and_no_type() {
10747 let cache = create_test_schema_cache();
10748 let names = crate::names::NameOverrides::parse(
10749 r#"{"tables": {"public.users": {"permissions":
10750 {"writer": {"insert": {"columns": "*", "check": {}}}}}}}"#,
10751 )
10752 .unwrap();
10753 let view = crate::role::cache_for_role(&cache, &names, "writer", false);
10754 let config = SchemaConfig {
10755 names: names.clone(),
10756 role: Some("writer".to_string()),
10757 ..SchemaConfig::default()
10758 };
10759 let generated = build_schema(&view, &config);
10760
10761 let schema = build_dynamic_schema(
10762 &generated,
10763 &view,
10764 None,
10765 None,
10766 Arc::new(names),
10767 std::time::Duration::from_secs(30),
10768 Some("writer"),
10769 )
10770 .expect("a role that may only write still has a buildable schema");
10771 let sdl = schema.sdl();
10772
10773 assert!(
10774 sdl.contains("insert_users("),
10775 "the write is there:\n{}",
10776 sdl
10777 );
10778 assert!(
10779 sdl.contains("users_insert_input"),
10780 "and its input:\n{}",
10781 sdl
10782 );
10783 assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10785 assert!(!sdl.contains("insert_users_one"), "no insert_one:\n{}", sdl);
10786 assert!(
10787 !sdl.contains("returning"),
10788 "and nothing to return:\n{}",
10789 sdl
10790 );
10791 assert!(
10792 sdl.contains("users_mutation_response"),
10793 "the count is still answered:\n{}",
10794 sdl
10795 );
10796 assert!(
10797 !sdl.contains("_mutation_response_mutation_response"),
10798 "and no type is named twice over:\n{}",
10799 sdl
10800 );
10801 }
10802
10803 fn cache_with_a_mutation_function() -> SchemaCache {
10806 use postrust_core::schema_cache::{FuncVolatility, RetType, Routine};
10807
10808 let mut cache = create_test_schema_cache();
10809 let routine = Routine {
10810 schema: "public".into(),
10811 name: "add_to_score".into(),
10812 description: None,
10813 params: Vec::new(),
10814 return_type: RetType::SetOf("users".into()),
10815 returns_composite: true,
10816 volatility: FuncVolatility::Volatile,
10817 has_variadic: false,
10818 isolation_level: None,
10819 settings: Vec::new(),
10820 is_procedure: false,
10821 media_type: None,
10822 media_base_type: None,
10823 output_columns: Vec::new(),
10824 };
10825 cache
10826 .routines
10827 .insert(routine.qualified_identifier(), vec![routine]);
10828 cache
10829 }
10830
10831 #[test]
10836 fn a_mutation_function_needs_the_role_to_be_named() {
10837 let cache = cache_with_a_mutation_function();
10838 let withheld = crate::names::NameOverrides::parse(
10839 r#"{"tables": {"public.users": {"permissions":
10840 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10841 "functions": {"public.add_to_score": {"exposed_as": "mutation"}}}"#,
10842 )
10843 .unwrap();
10844 let granted = crate::names::NameOverrides::parse(
10845 r#"{"tables": {"public.users": {"permissions":
10846 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10847 "functions": {"public.add_to_score":
10848 {"exposed_as": "mutation", "roles": ["reader"]}}}"#,
10849 )
10850 .unwrap();
10851
10852 let sdl_for = |names: &crate::names::NameOverrides| {
10853 let view = crate::role::cache_for_role(&cache, names, "reader", false);
10854 let config = SchemaConfig {
10855 names: names.clone(),
10856 role: Some("reader".to_string()),
10857 ..SchemaConfig::default()
10858 };
10859 let generated = build_schema(&view, &config);
10860 build_dynamic_schema(
10861 &generated,
10862 &view,
10863 None,
10864 None,
10865 Arc::new(names.clone()),
10866 std::time::Duration::from_secs(30),
10867 Some("reader"),
10868 )
10869 .expect("schema builds")
10870 .sdl()
10871 };
10872
10873 let without = sdl_for(&withheld);
10874 assert!(
10875 !without.contains("add_to_score"),
10876 "not granted, so not there:\n{}",
10877 without
10878 );
10879 assert!(
10882 !without.contains("mutation: mutation_root"),
10883 "and no mutation root at all:\n{}",
10884 without
10885 );
10886
10887 let with = sdl_for(&granted);
10888 assert!(
10889 with.contains("add_to_score"),
10890 "named by a function permission, so it is:\n{}",
10891 with
10892 );
10893 }
10894
10895 #[test]
10897 fn a_role_the_document_never_names_is_granted_nothing() {
10898 let cache = Arc::new(create_test_schema_cache());
10899 let config = SchemaConfig {
10900 names: crate::names::NameOverrides::parse(
10901 r#"{"tables": {"public.users": {"permissions":
10902 {"reader": {"select": {"columns": "*", "filter": {}}}}}}}"#,
10903 )
10904 .unwrap(),
10905 ..SchemaConfig::default()
10906 };
10907 let empty = build_ungranted_schema(&cache, &config).expect("it builds");
10908 let sdl = empty.sdl();
10909 assert!(
10910 sdl.contains("no_queries_available"),
10911 "nothing to read:\n{}",
10912 sdl
10913 );
10914 assert!(!sdl.contains("type users "), "and no table:\n{}", sdl);
10915 assert!(build_ungranted_schema(&cache, &SchemaConfig::default()).is_none());
10917 }
10918
10919 #[test]
10922 fn a_column_a_role_may_set_without_seeing_is_in_the_input_alone() {
10923 let cache = create_test_schema_cache();
10924 let names = crate::names::NameOverrides::parse(
10925 r#"{"tables": {"public.users": {"permissions":
10926 {"user": {"select": {"columns": ["id"], "filter": {}},
10927 "insert": {"columns": "*", "check": {}}}}}}}"#,
10928 )
10929 .unwrap();
10930 let view = crate::role::cache_for_role(&cache, &names, "user", false);
10931 let config = SchemaConfig {
10932 names: names.clone(),
10933 role: Some("user".to_string()),
10934 ..SchemaConfig::default()
10935 };
10936 let generated = build_schema(&view, &config);
10937 let object = &generated.object_types["users"];
10938 assert_eq!(
10939 object
10940 .fields
10941 .iter()
10942 .map(|f| f.name.as_str())
10943 .collect::<Vec<_>>(),
10944 vec!["id"],
10945 "the type shows what may be read"
10946 );
10947 assert!(
10948 object.writable_fields.iter().any(|f| f.name == "name"),
10949 "and the input keeps what may be written"
10950 );
10951
10952 let schema = build_dynamic_schema(
10953 &generated,
10954 &view,
10955 None,
10956 None,
10957 Arc::new(names),
10958 std::time::Duration::from_secs(30),
10959 Some("user"),
10960 )
10961 .expect("schema builds");
10962 let sdl = schema.sdl();
10963 let input = sdl
10964 .split("input users_insert_input")
10965 .nth(1)
10966 .and_then(|rest| rest.split('}').next())
10967 .expect("the insert input is registered");
10968 assert!(input.contains("name:"), "settable without being readable");
10969 }
10970
10971 #[test]
10975 fn a_generated_column_is_readable_and_not_writable() {
10976 let mut cache = create_test_schema_cache();
10977 cache
10978 .tables
10979 .get_mut(&postrust_core::api_request::QualifiedIdentifier::new(
10980 "public", "users",
10981 ))
10982 .expect("the fixture has users")
10983 .columns
10984 .get_mut("id")
10985 .expect("with an id")
10986 .always_generated = true;
10987
10988 let names = crate::names::NameOverrides::default();
10989 let config = SchemaConfig {
10990 names: names.clone(),
10991 ..SchemaConfig::default()
10992 };
10993 let generated = build_schema(&cache, &config);
10994 let schema = build_dynamic_schema(
10995 &generated,
10996 &cache,
10997 None,
10998 None,
10999 Arc::new(names),
11000 std::time::Duration::from_secs(30),
11001 None,
11002 )
11003 .expect("schema builds");
11004 let sdl = schema.sdl();
11005
11006 let block = |header: &str| {
11007 sdl.split(header)
11008 .nth(1)
11009 .and_then(|rest| rest.split('}').next())
11010 .unwrap_or_else(|| panic!("{} is registered:\n{}", header, sdl))
11011 .to_string()
11012 };
11013 assert!(
11014 block("type users ").contains("id:"),
11015 "still readable:\n{}",
11016 sdl
11017 );
11018 for header in ["input users_insert_input", "input users_set_input"] {
11019 assert!(
11020 !block(header).contains("id:"),
11021 "{} may not name it:\n{}",
11022 header,
11023 block(header)
11024 );
11025 }
11026 assert!(
11029 !sdl.contains("users_inc_input"),
11030 "and nothing is left to increment:\n{}",
11031 sdl
11032 );
11033 assert!(
11034 block("input users_insert_input").contains("name:"),
11035 "and the rest of the table is untouched",
11036 );
11037 }
11038
11039 #[test]
11040 fn test_create_object_type() {
11041 let table = create_test_table("users");
11042 let obj = TableObjectType::from_table(&table);
11043 let _gql_obj = create_object_type(&obj, &[], None);
11044 }
11045
11046 #[test]
11047 fn test_create_query_type() {
11048 let cache = create_test_schema_cache();
11049 let config = SchemaConfig::default();
11050 let generated = build_schema(&cache, &config);
11051
11052 let _query = create_query_type(
11053 &generated,
11054 "query_root",
11055 None,
11056 Arc::new(HashMap::new()),
11057 Arc::new(Default::default()),
11058 );
11059 }
11060
11061 #[test]
11062 fn test_create_mutation_type() {
11063 let cache = create_test_schema_cache();
11064 let config = SchemaConfig::default();
11065 let generated = build_schema(&cache, &config);
11066
11067 let _mutation = create_mutation_type(
11068 &generated,
11069 "mutation_root",
11070 Arc::new(HashMap::new()),
11071 Arc::new(HashMap::new()),
11072 Arc::new(Default::default()),
11073 None,
11074 None,
11075 );
11076 }
11077
11078 #[test]
11083 fn a_scalar_is_named_the_way_a_client_declares_it() {
11084 use crate::types::{pg_type_to_graphql, GraphQLType};
11085
11086 assert_eq!(pg_type_to_graphql("jsonb").to_string(), "jsonb");
11091 assert_eq!(pg_type_to_graphql("json").to_string(), "json");
11092 assert_eq!(pg_type_to_graphql("int8").to_string(), "bigint");
11093 assert_eq!(pg_type_to_graphql("numeric").to_string(), "numeric");
11094 assert_eq!(pg_type_to_graphql("timestamptz").to_string(), "timestamptz");
11095 assert_eq!(pg_type_to_graphql("timestamp").to_string(), "timestamp");
11096 assert_eq!(pg_type_to_graphql("uuid").to_string(), "uuid");
11097
11098 assert_eq!(pg_type_to_graphql("geometry").to_string(), "geometry");
11101 assert_eq!(
11102 pg_type_to_graphql("colors_enum"),
11103 GraphQLType::Custom("colors_enum".to_string())
11104 );
11105
11106 assert_eq!(
11108 leaf_scalar_name(&pg_type_to_graphql("_text")),
11109 "String".to_string()
11110 );
11111 }
11112
11113 #[test]
11118 fn every_table_gets_a_boolean_expression() {
11119 let cache = create_test_schema_cache();
11120 let config = SchemaConfig::default();
11121 let generated = build_schema(&cache, &config);
11122
11123 let (inputs, _) = crate::input::bool_exp::build_inputs(
11124 &generated.object_types,
11125 &generated.relationship_fields,
11126 );
11127 let names: HashSet<String> = inputs.iter().map(|i| i.type_name().to_string()).collect();
11128
11129 for table in generated.object_types.keys() {
11130 let expected = format!("{}_bool_exp", table);
11131 assert!(
11132 names.contains(&expected),
11133 "no {} among {:?}",
11134 expected,
11135 names
11136 );
11137 }
11138 assert!(names.contains("String_comparison_exp"));
11140 assert!(names.contains("Int_comparison_exp"));
11141 }
11142
11143 #[test]
11144 fn a_schema_carrying_the_boolean_expressions_still_builds() {
11145 let cache = create_test_schema_cache();
11146 let config = SchemaConfig::default();
11147 let generated = build_schema(&cache, &config);
11148
11149 let schema = build_dynamic_schema(
11150 &generated,
11151 &cache,
11152 None,
11153 None,
11154 Arc::new(Default::default()),
11155 std::time::Duration::from_secs(30),
11156 None,
11157 );
11158 assert!(schema.is_ok(), "{:?}", schema.err());
11159
11160 let sdl = schema.unwrap().sdl();
11161 assert!(
11162 sdl.contains("_bool_exp"),
11163 "no boolean expressions in:\n{}",
11164 sdl
11165 );
11166 assert!(sdl.contains("_and"), "no _and in the generated expressions");
11167 }
11168
11169 #[test]
11174 fn test_build_schema_with_subscriptions() {
11175 let cache = create_test_schema_cache();
11176 let config = SchemaConfig {
11177 enable_subscriptions: true,
11178 ..SchemaConfig::default()
11179 };
11180 let generated = build_schema(&cache, &config);
11181
11182 let sub_fields = generate_subscription_fields(&cache, &generated);
11184 assert!(!sub_fields.is_empty(), "Should have subscription fields");
11185
11186 let result = build_dynamic_schema(
11188 &generated,
11189 &cache,
11190 Some(&sub_fields),
11191 None,
11192 Arc::new(Default::default()),
11193 std::time::Duration::from_secs(30),
11194 None,
11195 );
11196 assert!(result.is_ok(), "Schema with subscriptions should build");
11197 }
11198
11199 #[test]
11200 fn test_subscription_field_generation() {
11201 let cache = create_test_schema_cache();
11202 let config = SchemaConfig::default();
11203 let generated = build_schema(&cache, &config);
11204
11205 let fields = generate_subscription_fields(&cache, &generated);
11206
11207 assert_eq!(fields.len(), 1);
11209 assert_eq!(fields[0].name, "users");
11210 assert_eq!(fields[0].table_name, "users");
11211 assert_eq!(fields[0].channel_name(), "postrust_public_users");
11212 }
11213
11214 #[test]
11215 fn test_create_subscription_type() {
11216 let cache = create_test_schema_cache();
11217 let config = SchemaConfig::default();
11218 let generated = build_schema(&cache, &config);
11219 let _subscription = create_subscription_type(
11222 &generated,
11223 "subscription_root",
11224 None,
11225 Arc::new(Default::default()),
11226 Arc::new(Default::default()),
11227 std::time::Duration::from_secs(30),
11228 );
11229 }
11230}
11231
11232#[derive(Debug, PartialEq, Eq)]
11234enum PathStep {
11235 Key(String),
11236 Index(usize),
11237}
11238
11239fn walk_json_path(value: &Value, path: &str) -> Result<Value, async_graphql::Error> {
11246 let mut at = value;
11247 for step in parse_json_path(path)? {
11248 let next = match (&step, at) {
11249 (PathStep::Key(key), Value::Object(map)) => map.get(key.as_str()),
11250 (PathStep::Index(index), Value::List(items)) => items.get(*index),
11251 _ => None,
11254 };
11255 match next {
11256 Some(next) => at = next,
11257 None => return Ok(Value::Null),
11258 }
11259 }
11260 Ok(at.clone())
11261}
11262
11263fn unclosed_bracket(path: &str) -> async_graphql::Error {
11265 let message = format!("\"{}\" is not a JSON path: a bracket is never closed", path);
11266 validation_error(&message)
11267}
11268
11269fn parse_json_path(path: &str) -> Result<Vec<PathStep>, async_graphql::Error> {
11276 let mut steps = Vec::new();
11277 let mut chars = path.chars().peekable();
11278 if chars.peek() == Some(&'$') {
11280 chars.next();
11281 }
11282 while let Some(&c) = chars.peek() {
11283 match c {
11284 '.' => {
11286 chars.next();
11287 }
11288 '[' => {
11289 chars.next();
11290 let quote = match chars.peek() {
11291 Some(&q @ ('\'' | '"')) => {
11292 chars.next();
11293 Some(q)
11294 }
11295 _ => None,
11296 };
11297 let mut inner = String::new();
11298 loop {
11299 let Some(c) = chars.next() else {
11300 return Err(unclosed_bracket(path));
11301 };
11302 match (quote, c) {
11303 (Some(quote), c) if c == quote => break,
11304 (None, ']') => break,
11305 _ => inner.push(c),
11306 }
11307 }
11308 if quote.is_some() {
11310 match chars.next() {
11311 Some(']') => {}
11312 _ => return Err(unclosed_bracket(path)),
11313 }
11314 }
11315 match (quote, inner.parse::<usize>()) {
11318 (None, Ok(index)) => steps.push(PathStep::Index(index)),
11319 _ => steps.push(PathStep::Key(inner)),
11320 }
11321 }
11322 _ => {
11323 let mut key = String::new();
11324 while let Some(&c) = chars.peek() {
11325 if matches!(c, '.' | '[') {
11326 break;
11327 }
11328 key.push(c);
11329 chars.next();
11330 }
11331 steps.push(PathStep::Key(key));
11332 }
11333 }
11334 }
11335 Ok(steps)
11336}
11337
11338#[cfg(test)]
11339mod json_path_tests {
11340 use super::*;
11341
11342 fn steps(path: &str) -> Vec<PathStep> {
11343 parse_json_path(path).expect("the path parses")
11344 }
11345
11346 fn key(name: &str) -> PathStep {
11347 PathStep::Key(name.to_string())
11348 }
11349
11350 #[test]
11351 fn the_document_itself_has_no_steps() {
11352 assert_eq!(steps("$"), vec![]);
11353 assert_eq!(steps(""), vec![]);
11354 }
11355
11356 #[test]
11357 fn a_key_is_written_bare_or_after_a_dot() {
11358 assert_eq!(steps("a"), vec![key("a")]);
11359 assert_eq!(steps(".obj.c1"), vec![key("obj"), key("c1")]);
11360 assert_eq!(steps("._underscore"), vec![key("_underscore")]);
11361 }
11362
11363 #[test]
11364 fn a_bracket_holds_an_index_or_a_quoted_key() {
11365 assert_eq!(steps("arr[0]"), vec![key("arr"), PathStep::Index(0)]);
11366 assert_eq!(steps("['!@#$%^']"), vec![key("!@#$%^")]);
11367 assert_eq!(
11368 steps("translations['hello world!']"),
11369 vec![key("translations"), key("hello world!")]
11370 );
11371 }
11372
11373 #[test]
11375 fn a_dot_before_a_bracket_changes_nothing() {
11376 let expected = vec![key("objs"), PathStep::Index(0), key("\u{4f60}\u{597d}")];
11377 for spelling in [
11378 "objs[0]['\u{4f60}\u{597d}']",
11379 "objs[0][\"\u{4f60}\u{597d}\"]",
11380 "objs[0].[\"\u{4f60}\u{597d}\"]",
11381 "objs.[0].[\"\u{4f60}\u{597d}\"]",
11382 ] {
11383 assert_eq!(steps(spelling), expected, "{}", spelling);
11384 }
11385 }
11386
11387 #[test]
11388 fn a_bracket_that_never_closes_is_refused() {
11389 assert!(parse_json_path("arr[0").is_err());
11390 assert!(parse_json_path("['key'").is_err());
11391 }
11392
11393 #[test]
11394 fn a_step_the_document_does_not_have_reads_null() {
11395 let document = Value::from_json(serde_json::json!({
11396 "obj": {"c1": "c2"},
11397 "arr": [1, 2, 3],
11398 }))
11399 .expect("a document");
11400 let at = |path| walk_json_path(&document, path).expect("the path parses");
11401 assert_eq!(at(".obj.c1"), Value::String("c2".to_string()));
11402 assert_eq!(at("arr[1]"), Value::Number(2.into()));
11403 assert_eq!(at("arr[9]"), Value::Null);
11404 assert_eq!(at(".obj.nothing"), Value::Null);
11405 assert_eq!(at("obj[0]"), Value::Null);
11408 assert_eq!(at("arr.first"), Value::Null);
11409 }
11410}